Good Bye 2022: 2023 is NEAR
A. Koxia and Whiteboards (CF 1770 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, m; cin >> n >> m; priority_queue<int> qwq; LL sum = 0; for(int i = 0; i < n; ++ i){ int a; cin >> a; sum += a; qwq.push(-a); } for(int i = 0; i < m; ++ i){ int b; cin >> b; sum += qwq.top(); sum += b; qwq.pop(); qwq.push(-b); } cout << sum << '\n'; } return 0; }
B. Koxia and Permutation (CF 1770 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; for(int i = 0; i + i < n; ++ i){ cout << n - i << ' '; if (n - i != i + 1) cout << i + 1 << ' '; } cout << '\n'; } return 0; }
C. Koxia and Number Theory (CF 1770 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; vector<LL> a(n); for(auto &i : a) cin >> i; auto check = [&](){ if (set<LL>(a.begin(), a.end()).size() != a.size()) return false; for(int i = 2; i <= n / 2; ++ i){ vector<int> used(i); for(auto x : a){ used[x % i] ++; } if (*min_element(used.begin(), used.end()) >= 2) return false; } return true; }; if (check()) cout << "YES" << '\n'; else cout << "NO" << '\n'; } return 0; }
当然,一开始的想法不是这样,我们考虑更相减损术。
即
如果其值不为,我们假设是 ,那么 就不能取值使得 是 的倍数,即 。
那么我们枚举 ,再枚举 ,如果存在 是 的倍数,那么 就有一个不能取的值。
当遍历完 后, 不能取的值覆盖了 ,那么就不存在 满足条件了。
否则就有一个满足条件的同余方程。
对所有都判断一遍,如果都有能取的值,同上,根据中国剩余定理可知一定有解。
而 的数量,根据鸽笼原理,一个 至多占一个位置,因此 枚举到 就可以了。
下面代码复杂度比较高,因为多了个判断差值是不是的倍数的循环,多了个,事先对差值分解质因数可以降到 。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL p_max = 100; LL pr[p_max], p_sz; void get_prime() { static bool vis[p_max]; FOR (i, 2, p_max) { if (!vis[i]) pr[p_sz++] = i; FOR (j, 0, p_sz) { if (pr[j] * i >= p_max) break; vis[pr[j] * i] = 1; if (i % pr[j] == 0) break; } } } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); get_prime(); int t; cin >> t; while(t--){ int n; cin >> n; vector<LL> a(n); for(auto &i : a) cin >> i; auto check = [&](){ for(int pp = 0; pp < p_sz; ++ pp){ int p = pr[pp]; int cnt = 0; vector<int> used(p); for(int i = 0; i < n; ++ i){ bool ok1 = false; for(int j = i + 1; j < n; ++ j){ LL dis = abs(a[i] - a[j]); if (dis == 0) return false; if (dis % p == 0){ ok1 = true; break; } } if (ok1){ if (!used[a[i] % p]){ ++ cnt; used[a[i] % p] = true; } } } if (cnt == p) return false; } return true; }; if (check()) cout << "YES" << '\n'; else cout << "NO" << '\n'; } return 0; }
D. Koxia and Game (CF 1770 D)
题目大意
和 玩游戏,有三个包含个数的数组,然后依次对 进行操作,得到数组
- 将 中的一个值丢掉
- 从剩下的两个数选一个数,作为
如果最终数组是一个排列,则 获胜,否则 获胜。
现在给定数组 ,假设两人绝顶聪明,问有多少个数组,满足存在必胜策略。
解题思路
手玩一下会发现取走后剩下的两个数一定是相同的,即让 取的数一定是确定的。感性原因如下:
首先,如果剩下的两个数有一个是先前取过的,那么 一定会取这个数,这样 就不是一个排列, 赢了。
因此,剩下的两个数必须都是 没取过的数,如果这两个数不一样,是,由于不确定 会取谁,在之后的状态里,必须还包含 这两个值,且还是个未取过的数,此时只要丢弃先前 选的 或 ,就能保证剩下的数一定都是 没取过的。但再之后的情况,就是我们不确定 是哪一个数没被取过,我们需要对每种情况去应对,此时就几乎做不到保证每次剩下的两个数都是 没取过的。
因此得出一个结论就是,对于 ,如果 ,那么 任取(随后丢掉 ),有 种方式 。而如果,那么要么 ,要么 , 丢掉仅出现一次的那个数,这样每次 面对两个相同的数无从选择,只能乖乖照做。
既然知道了数组的构造方法,那怎么判断和统计满足条件的数量呢。
首先对于的那些下标,其取值肯定是 ,且方案数是 。
然后我们考虑 的下标,究竟要选择 还是 。
一个朴素的想法就是搜索,假设选择 , 然后搜后续情况,再假设选,搜后续情况。
搜后续情况的时候,有个容易想到的技巧,就是如果我选择了 ,那么其他包含 的下标只能选另一个数了。
下标与下标之间由于 和 的关系构成了一张图。如果能在这张图上搜索就更好了。
考虑这张图该如何构造,无非就两种,一种是点是下标,边是数字,另一种是点是数字,边是下标。
分别考虑这两张图,后者才能完美的符合我们的要求:如果我选择了 ,那么我要找其他包含 的下标。而且前一张图的构造复杂度是会超时(
而如何体现我们选择了呢?
因为边是下标,连接了 和 ,如果我们选择了 ,那么我们就给该边赋予一个指向 的方向,那其他与的连边的方向只能背离 。最后如果所有数的入度都为,那么这是个可行的方案。
那方案数怎么统计呢?
首先由于连边,我们会得到若干个连通块,各个连通块的取值的乘积就是最终方案数。
由于只有个下标,要选出 个不同的数,对于一个连通块而言,如果它有个点,那意味着必须有 条边(即 个下标),才能达成个下标选 个数的条件,才有可能合法。否则:
- 如果一个连通块有个点, 条边,那意味着有一个点(数)不能被选到,则也不是一个排列
- 如果一个连通块有个点, 条边,那意味着有一个点(数)会被两个下标分别选了一次,则也不是一个排列
而对于 一个有 个点, 条边的连通块,其实就是一棵基环树(一棵树加一条边)。考虑对这个基环树的边赋予方向的话,容易发现只有两种情况:该基环数里唯一的环的方向是顺时针还是逆时针,而其他非环边的方向都是背离环的。
注意如果是自环的话,此时自环边的顺逆没有任何区别,此时的方案数应是上面提到的,即 任意取。
因此总结以上思考可以得出,如果各个连通块均满足点数等于边数,则存在数组。而有自环的连通块贡献答案,无自环的贡献 ,答案就是这些数的累乘。
假设有自环的连通块数量是,无自环的是 ,那答案就是
而由于一条边贡献了两个点的度数,因此点数等于边数的条件可以转换为2倍点数等于点度数和。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL mo = 998244353; long long qpower(long long a, long long b){ long long qwq = 1; while(b){ if (b & 1) qwq = qwq * a % mo; a = a * a % mo; b >>= 1; } return qwq; } long long inv(long long x){ return qpower(x, mo - 2); } const LL inv2 = inv(2); int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t--){ int n; cin >> n; vector<int> du(n, 0), sz(n, 1); vector<int> fa(n); iota(fa.begin(), fa.end(), 0); vector<int> a(n), b(n); LL ans = 1; for(auto &i : a){ cin >> i; -- i; } for(auto &i : b){ cin >> i; -- i; } function<int(int)> findfa = [&](int x){ return x == fa[x] ? x : fa[x] = findfa(fa[x]); }; auto calc = [&](){ for(int i = 0; i < n; ++ i){ du[a[i]] ++; du[b[i]] ++; if (a[i] == b[i]){ ans = ans * n % mo * inv2 % mo; } } for(int i = 0; i < n; ++ i){ int fx = findfa(a[i]); int fy = findfa(b[i]); if (fx != fy){ fa[fx] = fy; du[fy] += du[fx]; sz[fy] += sz[fx]; } } for(int i = 0; i < n; ++ i){ int ff = findfa(i); if (ff == i){ debug(ff, du[ff], sz[ff]); if (du[ff] != sz[ff] * 2) return false; ans = ans * 2 % mo; } } return true; }; if (calc()){ cout << ans << '\n'; }else cout << 0 << '\n'; } return 0; }
E. Koxia and Tree (CF 1770 E)
题目大意
给定一棵包含个节点的树,第条边连接了节点 。有只蝴蝶在树的节点上,第只蝴蝶在节点 上。一个节点至多有一只蝴蝶。
依次进行以下操作:
- 依次对于边,等概率为该边赋一个方向
- 依次对于边,如果边的起始点有蝴蝶,且终点没有蝴蝶,则该蝴蝶会飞到终点上
最后,等概率选择两只蝴蝶,计算它们的距离,即边数。
问该边数的期望值。
解题思路
虽然初看这题感觉是神仙期望题,但按照套路来还是不难。
由期望的线性可加性,且所有情况都是等概率的,既然是边数,我们就考虑每条边对期望的贡献。
如果仅仅是选择两个蝴蝶问它们的期望距离,对于一条边来说,它对答案的贡献次数就是 ,其中就是该边深度更深的节点的子树的蝴蝶数。
如果多了移动操作,那么 的值就不是固定了,而是有概率的。
而边对答案的贡献次数就变成了期望次数(?存疑,还在思考
神奇的代码
F. Koxia and Sequence (CF 1770 F)
题目大意
<++>
解题思路
<++>
神奇的代码
G. Koxia and Bracket (CF 1770 G)
题目大意
<++>
解题思路
<++>
神奇的代码
H. Koxia, Mahiru and Winter Festival (CF 1770 H)
题目大意
<++>
解题思路
<++>
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17017053.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步