A 水上由崎的字符串
出题人:Lynia
题意描述
倒序输出字符串。「YAC Round 12」水上由崎的字符串
解题思路 (语法题)
直接倒序输出,或者用 C++ 的 reverse
更改整个字符串,也可以直接用 Python 的切片。
1 2 3 4 5 6 7 8 9 10 #include <bits/stdc++.h> using namespace std;int main () { int T; cin >> T; while (T--) { string a; cin >> a; for (int i = a.size () - 1 ; i >= 0 ; i--) cout << a[i]; cout << '\n' ; } }
1 2 3 4 5 6 7 8 9 10 #include <bits/stdc++.h> using namespace std;int main () { int T; cin >> T; while (T--) { string a; cin >> a; reverse (a.begin (), a.end ()); cout << a << '\n' ; } }
1 2 T = int (input ())for i in range (T): print (input ()[::-1 ])
B 三分之王
出题人:MarisaMagic
题意描述
已知球筐位于 ( 0 , 0 ) (0, 0) ( 0 , 0 ) ,三分线为一个半径 7.25 m 7.25m 7.25 m 的圆弧,投篮点可由 ( x , y ) (x, y) ( x , y ) 表示(单位为 m m m )。统计命中的投篮中有多少个是三分球。(“bang!!!” 为投篮命中,“missed” 为未命中)「YAC Round 12」三分之王
解题思路 (简单计算几何)
对于每次投篮,先判断是否进球,再判断是否满足 x 2 + y 2 > 7.2 5 2 x ^ 2 + y ^ 2 > 7.25^2 x 2 + y 2 > 7.2 5 2 or x 2 + y 2 > 7.25 \sqrt{x ^ 2 + y ^ 2} > 7.25 x 2 + y 2 > 7.25 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <bits/stdc++.h> using namespace std;#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) void marisa () { int n, ans = 0 ; cin >> n; for (int i = 1 ; i <= n; i ++ ){ string s; cin >> s; double x, y; cin >> x >> y; ans += (s[0 ] == 'b' && x * x + y * y > 7.25 * 7.25 ); } cout << ans << "\n" ; }int main () { ios; int T = 1 ; while (T -- ) marisa (); return 0 ; }
C 大空魔术
出题人:MarisaMagic
题意描述
给定一个无数个节点的带权无向完全图,编号 1 ∼ + ∞ 1 \sim +\infty 1 ∼ + ∞ ,两个不同节点 i i i 和 j j j 之间的边的长度为 lcm ( i , j ) \text{lcm}(i, j) lcm ( i , j ) 。有 m m m 次询问,每次询问两个节点 x , y x, y x , y ,求两个节点之间的最短距离。 「YAC Round 12」大空魔术
解题思路 (数学)
先判断 x = y x = y x = y 的情况,此时答案为 0 0 0 ;
若走 1 1 1 步,最短的距离就是 lcm ( x , y ) \text{lcm}(x, y) lcm ( x , y ) ;
若走 2 2 2 步,第一步一定 ≥ x \ge x ≥ x ,第二步一定 ≥ y \ge y ≥ y ,因此走 2 2 2 步所得的距离一定满足 ≥ x + y \ge x + y ≥ x + y 。显然,x x x 到 y y y 的走 2 2 2 步的最短距离可以在 lcm ( 1 , x ) + lcm ( 1 , y ) = x + y \text{lcm}(1, x) + \text{lcm}(1, y) = x + y lcm ( 1 , x ) + lcm ( 1 , y ) = x + y 时取到。
若走大于 2 2 2 步,显然不会比只走 2 2 2 步更优。因此当 x ≠ y x \not = y x = y 时,答案就是 min ( lcm ( x , y ) , x + y ) \min(\text{lcm}(x, y), x + y) min ( lcm ( x , y ) , x + y ) 。但是,数据范围为 1 ≤ x , y ≤ 1 0 18 1 \le x, y \le 10^{18} 1 ≤ x , y ≤ 1 0 18 ,直接求最小公倍数会导致数据溢出(为此本题也卡掉了python)
我们假定 x < y x < y x < y ,gcd ( x , y ) \gcd(x, y) g cd( x , y ) 表示 x , y x, y x , y 的 最大公约数 ,分类讨论 lcm ( x , y ) \text{lcm}(x, y) lcm ( x , y ) 与 x + y x + y x + y 的大小关系:
若 gcd ( x , y ) = x \gcd(x, y) = x g cd( x , y ) = x ,可以推出:
lcm ( x , y ) = y < x + y \text{lcm}(x, y) = y < x + y
lcm ( x , y ) = y < x + y
故此时答案为 y y y 。
若 gcd ( x , y ) ≠ x \gcd(x, y) \not = x g cd( x , y ) = x ,则表明 x gcd ( x , y ) ≥ 2 \frac{x}{\gcd(x, y)} \ge 2 g c d ( x , y ) x ≥ 2 ,由此我们可以推出:
lcm ( x , y ) = x × y gcd ( x , y ) ≥ 2 y > x + y \text{lcm}(x, y) = \frac{x \times y}{\gcd(x, y)} \ge 2 y > x + y
lcm ( x , y ) = g cd( x , y ) x × y ≥ 2 y > x + y
故此时答案为 x + y x + y x + y 。
我们甚至可以不用求 gcd \gcd g cd 来进行判断,gcd ( x , y ) = x ⇔ y m o d x = 0 \gcd(x, y) = x \Leftrightarrow y \mod x = 0 g cd( x , y ) = x ⇔ y mod x = 0 是一个充要条件,因此只需要看是否满足 y m o d x = 0 y \mod x = 0 y mod x = 0 条件判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <bits/stdc++.h> using namespace std;#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) using ll = long long ;void marisa () { ll x, y; cin >> x >> y; if (x == y){ cout << 0 << "\n" ; return ; } if (x > y) swap (x, y); cout << (y % x == 0 ? y : x + y) << "\n" ; }int main () { ios; int T = 1 ; cin >> T; while (T -- ) marisa (); return 0 ; }
D 華鳥風月
出题人:MarisaMagic
题意描述
给定一颗 n n n 个节点的树,每个节点为黑色或白色。求 相邻节点颜色均不相同 的最长简单路径的长度(节点个数)。「YAC Round 12」華鳥風月
解题思路 (树的直径 树形DP / 两次DFS)
以下称满足 相邻节点颜色均不相同 的最长简单路径为“合法路径”。
我们设定节点 1 1 1 为树的根节点,考虑 树形DP 。设 f 1 [ u ] f_1[u] f 1 [ u ] 为 u u u 为根的子树向下所能延伸的 最长 合法路径长度,f 2 [ u ] f_2[u] f 2 [ u ] 为 u u u 为根的子树向下所能延伸的 次长 合法路径长度。要求得整棵树的最长合法路径长度,枚举每个节点 i i i 作为中间的点,找出 f 1 [ i ] + f 2 [ i ] + 1 f_1[i] + f_2[i] + 1 f 1 [ i ] + f 2 [ i ] + 1 最大值即可(路径长度定义为节点个数,因此要 $ + 1$)。
可以发现上述做法和求解 树的直径 是完全一致的,但需要在状态转移过程中增加节点颜色的判断。自底向上从 v v v 转移到 u u u 时,如果颜色不同,用 f 1 [ v ] + 1 f_1[v] + 1 f 1 [ v ] + 1 对 u u u 为根的子树所能延伸的最长和次长长度正常更新;如果颜色相同,则不能走过去,直接跳过即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <bits/stdc++.h> using namespace std;#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) const int N = 1e5 + 10 ;int n, ans, c[N], f1[N], f2[N]; vector<int > e[N];void dfs (int u, int fa) { for (const auto &v : e[u]){ if (v == fa) continue ; dfs (v, u); if (c[u] == c[v]) continue ; int tmp = f1[v] + 1 ; if (tmp > f1[u]) f2[u] = f1[u], f1[u] = tmp; else if (tmp > f2[u]) f2[u] = tmp; } ans = max (ans, f1[u] + f2[u] + 1 ); }void marisa () { cin >> n; for (int i = 1 ; i <= n; i ++ ) cin >> c[i]; for (int i = 1 , u, v; i < n; i ++ ){ cin >> u >> v; e[u].emplace_back (v); e[v].emplace_back (u); } dfs (1 , 0 ); cout << ans << "\n" ; }int main () { ios; int T = 1 ; while (T -- ) marisa (); return 0 ; }
我们也可以用另一种树形 DP 方法。定义 f [ u ] f[u] f [ u ] 为 u u u 为根的子树向下所能延伸的 最长 合法路径长度。类似于 树的直径 OI Wiki 中 过程2 的做法,增加一个节点颜色判断即可。
1 2 3 4 5 6 7 8 9 10 11 void dfs (int u, int fa) { for (auto &v : e[u]){ if (v == fa) continue ; dfs (v, u); if (c[u] ^ c[v]){ ans = max (ans, dp[u] + dp[v] + 2 ); dp[u] = max (dp[u], dp[v] + 1 ); } } }
一条边上节点颜色相同,我们的操作是直接跳过,边上端点所属的子树之间结果互不影响。因此可以在建树的时候忽略这些端点颜色相同的边,构建若干个小树。最终我们只需要在每个小树上单独进行 树形DP / 两次DFS 求得树的直径,再取最大值即为答案。以下给出忽略端点颜色相同的边,用 两次DFS 求解的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <bits/stdc++.h> using namespace std;#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) const int N = 1e5 + 10 ;int n, ans, id, c[N], dist[N]; vector<int > e[N];void dfs (int u, int fa) { for (auto &v : e[u]){ if (v == fa) continue ; dist[v] = dist[u] + 1 ; if (dist[v] > dist[id]) id = v; dfs (v, u); } }void marisa () { cin >> n; for (int i = 1 ; i <= n; i ++ ) cin >> c[i]; for (int i = 1 , u, v; i < n; i ++ ){ cin >> u >> v; if (c[u] + c[v] != 1 ) continue ; e[u].emplace_back (v); e[v].emplace_back (u); } for (int i = 1 ; i <= n; i ++ ){ id = 0 ; dfs (i, 0 ); dist[id] = 0 ; dfs (id, 0 ); ans = max (ans, dist[id] + 1 ); } cout << ans << "\n" ; }int main () { ios; int T = 1 ; while (T -- ) marisa (); return 0 ; }
E 交叉星尘
出题人:MarisaMagic
题意描述
给定一个 n n n 个节点的带权无向图,有 m m m 个集合,第 i i i 个集合可表示为 P i = { ( v 1 , b 1 ) , ( v 2 , b 2 ) , … , ( v ∣ P ∣ , b ∣ P ∣ ) } P_i = \{(v_1, b_1), (v_2, b_2), \ldots, (v_{|P|}, b_{|P|}) \} P i = {( v 1 , b 1 ) , ( v 2 , b 2 ) , … , ( v ∣ P ∣ , b ∣ P ∣ )} 。对于同一集合中的任意两个节点 ( v i , b i ) , ( v j , b j ) (v_i, b_i), (v_j, b_j) ( v i , b i ) , ( v j , b j ) ,有一条连接 v i v_i v i 和 v j v_j v j 的边权为 b i + b j b_i + b_j b i + b j 的无向边。对于节点 i ∈ [ 1 , n ] i\in [1, n] i ∈ [ 1 , n ] ,求 1 1 1 到 i i i 的最短路长度。「YAC Round 12」交叉星尘
解题思路 (dijkstra,构造)
直接暴力建图跑 d i j k s t r a dijkstra d ijk s t r a 最短路,时间复杂度为 O ( ( n + ∑ ∣ P i ∣ 2 ) log ∑ ∣ P i ∣ 2 ) \mathcal{O}((n + \sum{|P_i|^2})\log{\sum{|P_i|^2}}) O (( n + ∑ ∣ P i ∣ 2 ) log ∑ ∣ P i ∣ 2 ) ,但是会导致边数太大 MLE。因此需要考虑构造转变建图方式来优化空间。
对于每个集合,考虑构建一个 虚点 x x x 。对于同一个集合的每个点 ( v i , b i ) (v_i, b_i) ( v i , b i ) ,在 v i v_i v i 与 x x x 之间连接一条边权 b i b_i b i 的无向边。由此将原本的完全图转换为了 菊花图 ,边的数量大大减少。
在转换构建的图中,相当于将原先的一条边权为 b i + b j b_i + b_j b i + b j 的边拆成了两条分别长为 b i b_i b i 和 b j b_j b j 的边。这些拆分后的边通过被重复利用以达到 l e n g t h ( v i , v j ) = b i + b j , ∀ i , j ∈ [ 1 , ∣ P ∣ ] length_{(v_i, v_j)} = b_i + b_j, \forall \ i, j\in [1, |P|] l e n g t h ( v i , v j ) = b i + b j , ∀ i , j ∈ [ 1 , ∣ P ∣ ] ,所构建出的图与原图等价。
故在转换构建出的图上跑一遍 d i j k s t r a dijkstra d ijk s t r a 最短路即可,时间复杂度: O ( ( n + ∑ ∣ P i ∣ ) log ∑ ∣ P i ∣ ) \mathcal{O}((n + \sum{|P_i|})\log{\sum{|P_i|}}) O (( n + ∑ ∣ P i ∣ ) log ∑ ∣ P i ∣ ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> using namespace std;#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) const int N = 6e5 + 10 ;using ll = long long ;using pll = pair<ll, ll>;int n, m, st[N]; vector<pll> e[N]; ll dist[N];void dijkstra () { memset (dist, 0x3f , sizeof dist); priority_queue<pll, vector<pll>, greater<pll> > q; q.emplace (dist[1 ] = 0 , 1 ); while (q.size ()){ auto [d, u] = q.top (); q.pop (); if (st[u]) continue ; st[u] = true ; for (const auto &[v, w] : e[u]) if (dist[v] > d + w) q.emplace (dist[v] = d + w, v); } }void marisa () { cin >> n >> m; for (int i = 1 , k; i <= m; i ++ ){ cin >> k; int u = n + i; for (int j = 1 , v, w; j <= k; j ++ ){ cin >> v >> w; e[u].emplace_back (v, w); e[v].emplace_back (u, w); } } dijkstra (); for (int i = 1 ; i <= n; i ++ ) cout << dist[i] << " \n" [i == n]; }int main () { ios; int T = 1 ; while (T -- ) marisa (); return 0 ; }
F 就在你身边
出题人:Lynia
题意描述
给你长度为 n n n 的序列 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots, a_n a 1 , a 2 , ⋯ , a n ,你可以进行最多 k k k 次操作,每次操作可以把一个元素增加或减少 1 1 1 ,求操作后最长的连续相等的子串。「YAC Round 12」就在你身边
解题思路 (双指针,中位数性质,双堆模拟)
很明显这题可以用双指针开个滑动窗口 解决。问题是枚举的每个区间该全等于什么值,这里就使用上了中位数性质 ——最小化绝对偏差 ,中位数是使得绝对偏差和(即所有数据点与中位数的绝对差值之和)最小的值。
数学上,这可以表示为:
Minimize ∑ i = 1 n ∣ x i − M ∣ \text{Minimize} \sum_{i=1}^{n} |x_i - M|
Minimize i = 1 ∑ n ∣ x i − M ∣
其中,M M M 为中位数,x i x_i x i 为数据集中的各个数值。
知道了这个性质后,我们找到滑动窗口枚举的区间的中位数,直接计算区间所有值变成中位数要的总操作次数就行,根据总操作是否超过 k k k 来判断慢指针的移动。
至于中位数的维护,我们这边使用两个 multiset
即可,当然过程中对这两个双堆要进行不断调整,具体实现参考题解代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #include <bits/stdc++.h> using namespace std;#define fa(i,op,n) for (int i = op; i <= n; i++) #define fb(j,op,n) for (int j = op; j >= n; j--) #define var auto #define all(i) i.begin(), i.end() #define endl '\n' using ll = long long ;void solve () { ll n, k; cin >> n >> k; var a = vector <ll>(n + 1 , 0 ); fa (i, 1 , n)cin >> a[i]; multiset<ll>r; multiset<ll, greater<ll>>l; ll l_sum = 0 , r_sum = 0 ; var f = [&](int len)->void { ll tmp; while (l.size () and r.size () and (*l.begin ()) > (*r.begin ())) { tmp = (*l.begin ()); r_sum += tmp, r.insert (tmp); l_sum -= tmp, l.erase (l.begin ()); } if (len & 1 ) { while (l.size () < r.size () + 1 ) { tmp = (*r.begin ()); l_sum += tmp, l.insert (tmp); r_sum -= tmp, r.erase (r.begin ()); } while (l.size () > r.size () + 1 ) { tmp = (*l.begin ()); r_sum += tmp, r.insert (tmp); l_sum -= tmp, l.erase (l.begin ()); } } else { while (l.size () < r.size ()) { tmp = (*r.begin ()); l_sum += tmp, l.insert (tmp); r_sum -= tmp, r.erase (r.begin ()); } while (l.size () > r.size ()) { tmp = (*l.begin ()); r_sum += tmp, r.insert (tmp); l_sum -= tmp, l.erase (l.begin ()); } } }; ll j = 1 ; ll ans = 1 ; fa (i, 1 , n) { if (l.size () == 0 )l_sum += a[i], l.insert (a[i]); else { if (a[i] <= (*l.begin ()))l_sum += a[i], l.insert (a[i]); else r_sum += a[i], r.insert (a[i]); } f (i - j + 1 ); ll mmid = (*l.begin ()); ll now = (mmid * (ll)l.size () - l_sum) + (r_sum - mmid * (ll)r.size ()); while (now > k and j < i) { if (l.count (a[j])) l_sum -= a[j], l.erase (l.find (a[j])); else r_sum -= a[j], r.erase (r.find (a[j])); j++; f (i - j + 1 ); mmid = (*l.begin ()); now = (mmid * (ll)l.size () - l_sum) + (r_sum - mmid * (ll)r.size ()); } ans = max (ans, i - j + 1 ); } cout << ans << endl; return ; }int main () { ios::sync_with_stdio (0 ), cin.tie (0 ), cout.tie (0 ); int _ = 1 ; fa (i, 1 , _)solve (); return 0 ; }