Codeforces Round 868 Div 2
A. A-characteristic (CF 1823 A)
题目大意
要求构造一个仅包含和 的长度为 的数组 ,使得存在 个下标对 满足 。
解题思路
当有个 , 个 时,其满足条件的下标对数量为 。
由于只有 ,直接枚举 即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t--){ int n, k; cin >> n >> k; int one = 0; for(; one <= n; ++ one){ int neg = n - one; if (neg * (neg - 1) + one * (one - 1) == 2 * k) break; } if (one > n) cout << "No" << '\n'; else{ cout << "Yes" << '\n'; for(int i = 0; i < one; ++ i) cout << 1 << ' '; for(int i = 0; i < n - one; ++ i) cout << -1 << ' '; cout << '\n'; } } return 0; }
B. Sort with Step (CF 1823 B)
题目大意
给定一个排序,问能否仅通过交换相隔的俩元素,使得有序。不能的话问能否事先通过一次任意交换操作后,再通过之前的操作交换得到有序。
解题思路
考虑每个元素的原始位置和最后所在的位置,它们对的取模应该相同。否则就不能有序。
而如果恰好有两个元素其对的取模不同,且交换之后是相同的,则可以。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t--){ int n, k; cin >> n >> k; vector<int> a(n); for(auto &i : a){ cin >> i; i --; } bool ok1 = true; map<pair<int, int>, int> cnt; for(int i = 0; i < n; ++ i){ if (i % k != a[i] % k){ ok1 = false; cnt[{min(i % k, a[i] % k), max(i % k, a[i] % k)}] ++; } } if (ok1) cout << 0 << '\n'; else if (cnt.size() == 1 && cnt.begin()->second == 2) cout << 1 << '\n'; else cout << -1 << '\n'; } return 0; }
C. Strongly Composite (CF 1823 C)
题目大意
给定一个数组,构造数组 ,要求最大化数组的元素数量,使得俩数组的所有元素的乘积相同,且数组 的每个数都是强合数。
强合数的定义为,合数因子数量质数因子数量。
解题思路
乘积相同,相当于将数组里的质数重新组合;数量最大,相当于尽可能少用质数来组成一个新的数。
可以发现,两个相同的质数组成一个强合数,或者三个不同的质数可以组成一个强合数。
由此我们统计数组 中的每个质数的数量,同个质数俩俩组合,不同质数三三组合,就能最大化答案了。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t--){ int n; cin >> n; map<int, int> cnt; auto fac = [&](int a){ for(int i = 2; i * i <= a; ++ i){ while (a % i == 0){ cnt[i] ++; a /= i; } } if (a != 1) cnt[a] ++; }; for(int i = 0; i < n; ++ i){ int a; cin >> a; fac(a); } LL ans = 0; int left = 0; for(auto &[_, value] : cnt){ ans += value / 2; left += (value & 1); } ans += left / 3; cout << ans << '\n'; } return 0; }
D. Unique Palindromes (CF 1823 D)
题目大意
要求构造一个仅包含小些字母的字符串,长度为,且满足 个限制。
每个限制表述为, 字符串的长度为 的前缀满足有 个本质不同的回文串)
解题思路
通过打表发现本质不同的回文串数量不会超过字符串长度。
注意到最大只有 ,这启示我们每个限制可以用一个字符去满足。
思考朴素的构造方法,对于一个长度为 的字符串,我们可以 这样去构造,一开始连续的 的数量就能控制这个字符串的本质不同的回文串的数量。这样的构造方法满足其数量在 之内,这刚好符合题意里 的限制。
因此我们可以先根据第一个限制构造出如上的字符串,对于之后的限制进行增量构造,增加的回文数量用 , 这样构造,剩下的长度用 这样不会增加回文串数量的形式去填充。
注意用于填充的字符串,在每次填充时应该继续前面的,而不是从头(从 )开始(如代码的fill_cur
),不然可能会新增回文串。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t--){ int n, k; cin >> n >> k; vector<int> x(k), c(k); for(auto &i : x) cin >> i; for(auto &i : c) cin >> i; string ans; int fill_cur = 0; auto fill = [&](int x){ while(x--){ ans.push_back('a' + fill_cur); fill_cur = (fill_cur + 1) % 3; } }; auto ok = [&](){ int cur = 0; for(int i = 0; i < k; ++ i){ int dis = x[i] - c[i]; if (dis < 0) return false; if (cur > dis) return false; cur = dis; } ans += string(c[0] - 3, 'a'); fill(x[0] - ans.size()); for(int i = 1; i < k; ++ i){ ans += string(c[i] - c[i - 1], 'c' + i); fill(x[i] - ans.size()); } return true; }; if (ok()){ cout << "YES" << '\n'; cout << ans << '\n'; }else { cout << "NO" << '\n'; } } return 0; }
E. Removing Graph (CF 1823 E)
题目大意
两人博弈。
给定个环,每个人可以从 中选一个数,然后选择由 个点组成的连通子图,将点及其边去掉。不能操作者输。
在绝顶聪明的情况下,问先后手谁必赢。
解题思路
每个环都是一个独立局面,因此我们求出每个环的值,异或起来,非零就先手赢,否则后手赢。
对于一个环来说,取了一次之后就变成一条链了。因此一个环的 值就是所有可能的链的长度对应的值的 。
对于一个链来说,取了一次之后就变成两条链,这两条链分别都是一个独立局面,因此一个链的 值,就是一些操作值的 , 而操作值就是取了之后(有取的长度和取的位置两个因素)的两个链的值的异或。
注意到题目保证了 ,对于一条链来说,如果它能取(即长度 ),则必定能分成两条长度一样的链,之后先手就模仿后手的操作,就能必赢了。
也就是说,对于一个环来说,如果其长度,那么先手取了一次后,变成的链因为后手必定可以再取( ),所以对于后手来说必定是个必胜态,所以这样的环对于先手来说必定是个必败态,其 值为 。
而长度小于 ,不能取,那肯定是必败态,其 值为 。
考虑环长度 在 之间的值,其对应的链长度有 。其值就是这些可能的链长度的 值取 。
考虑链长度,小于 ,是必败态,其 值为 。 而,
上述的里的每一项都是取最边边的结果(即取了之后还有一个链),至于有两条链的结果,是长度更小的两个链的的异或值,其不会超过上面的最大值。
由此(或打表)可以发现长度为
进而环的
环的值求出来了,异或一下就知道谁赢了。
至于环大小,用并查集或一下就知道了。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; class dsu { public: vector<int> p; vector<int> sz; int n; dsu(int _n) : n(_n) { p.resize(n); sz.resize(n); iota(p.begin(), p.end(), 0); fill(sz.begin(), sz.end(), 1); } inline int get(int x) { return (x == p[x] ? x : (p[x] = get(p[x]))); } inline bool unite(int x, int y) { x = get(x); y = get(y); if (x != y) { p[x] = y; sz[y] += sz[x]; return true; } return false; } }; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, l, r; cin >> n >> l >> r; dsu d(n); for(int i = 0; i < n; ++ i){ int u, v; cin >> u >> v; -- u; -- v; d.unite(u, v); } int ans = 0; for(int i = 0; i < n; ++ i){ if (d.get(i) == i){ if (d.sz[i] >= l && d.sz[i] < l + r) ans ^= d.sz[i] / l; } } if (ans) cout << "Alice" << '\n'; else cout << "Bob" << '\n'; return 0; }
F. Random Walk (CF 1823 F)
题目大意
树上随机游走,从点到点 ,问每个点访问次数的期望值。
解题思路
每次的期望题感觉都比较神仙。
注意到这是棵树,点到点的路径是唯一的,设路径为。
一开始设状态表示从 点到 点,期望访问到 号点的次数,然后枚举到相邻点的状态,即,但感觉怎么都算不出来。
然后想着从点 出发,它可以往很多个相邻点走,只有一个点是更接近点 的, 且最终到点时立刻停下来,这意味着点之后的点的访问次数的期望值一定是 。
考虑到一旦走到点时,发现问题貌似变成了一个子问题了,可以认为是从点出发,到点 的情况。换句话说,我们可以将 点到点 的步骤分成若干步,分别是点 到点 ,点 到点 ... 点到点 ,由于期望的线性可加性,每个点的期望访问次数,可以由这些的每个步骤的影响依次累计。
设 表示从 点往更接近点的方向走(即走到 点),对 点的期望访问次数。
设点 的度数为 ,其余字母定义看图,根据期望定义,可以得到:
这里有三个部分:
- 有的概率选择走到 ,然后就停下来了,此时对 的访问贡献是。
- 有的概率往 所在的子树走(即点 ),此时对的访问贡献是由和组成,即 。
- 有的概率往其他子树走(即点 表示的其他所有点),此时对的访问贡献是由和组成,即 。但由于从点到点必须经过点 ,而一旦到点 就会停下来( 即表示从点 到更接近 点的方向走(即往点 ),对点 的访问贡献),因此 。
这样上述式子移一下项,就得到
即点往点 走时的对点访问次数的贡献是等价于点 往点 走时,对点 的贡献。由此就可以得到
剩下的就是求 。根据期望定义,可以得到
这里有两部分:
- 有的概率选择走到 ,此时就停下来了,因此对的访问贡献是(一开始在时的贡献)。
- 有的概率选择走到除点之外的其他点(设点,即 或 ),因此对的访问贡献是和 ,即,而因为点到点 就会停下来(点 是更接近点 的点),因此 (一开始在时的贡献包含在 里)。
这样上述式子移一下项,就得到
综合上述的两个式子,可以得出,每当进行一次 时,其他点的期望访问次数都会增加 ,其中点是点 除了 方向的其他方向的点(见上图的虚线包括起来,就是对应颜色的箭头的影响)。
也就是说一个点的期望访问次数就是,其中 等于该点与路径的交点(以上图为例,就为 )到 的点数(见上图的点)。
剩下的就是如何求 ,我们可以以点 为根,然后我们从点 开始,一路沿着 父亲节点上去,就回到点,其中每往父亲跳一次时, 就会加一,比如从时, ,此时再遍历一下除了和 方向的所有点 ,它们的答案就是 。
最终的时间复杂度是。
虽然答案不会超过,但记得对取模。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int mod = 998244353; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, s, t; cin >> n >> s >> t; -- s, -- t; vector<vector<int>> edge(n); vector<int> du(n); for(int i = 1; i < n; ++ i){ int u, v; cin >> u >> v; -- u, -- v; edge[u].push_back(v); edge[v].push_back(u); ++ du[u]; ++ du[v]; } vector<int> fa(n); function<void(int, int)> dfs = [&](int u, int f){ fa[u] = f; for(auto &v : edge[u]){ if (v == f) continue; dfs(v, u); } }; dfs(s, s); vector<int> ans(n); int cnt = 1; ans[t] = 1; function<void(int, int, int)> dfs2 = [&](int u, int f, int cnt){ ans[u] = 1ll * du[u] * cnt % mod; for(auto &v : edge[u]){ if (v == f) continue; dfs2(v, u, cnt); } }; do{ int cur = fa[t]; ans[cur] = 1ll * cnt * du[cur] % mod; for(auto &u : edge[cur]){ if (u != fa[cur] && u != t) dfs2(u, cur, cnt); } t = cur; ++ cnt; }while(s != t); for(auto &i : ans) cout << i << ' '; cout << '\n'; return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17363927.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步