Good Bye 2022: 2023 is NEAR
A. Koxia and Whiteboards (CF 1770 A)
题目大意
给定一个包含\(n\)个数的数组 \(a\),以及 \(m\)次操作。
第\(i\)次操作将 \(a\) 中的一个数替换为\(b_i\)。
问 \(m\)次操作后数组 \(a\)的和的最大值。
解题思路
直接贪心,每次选最小的数替换称\(b_i\)即可。
用优先队列维护最小值。
神奇的代码
#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)
题目大意
给定\(n, k\),定义一个长度为 \(n\)的排列的价值为
请构造一个排列,是其价值最小。
解题思路
当 \(k\)等于 \(1\)时,无论怎么构造代价都是 \(2n\)。
当 \(k\)更大时,看样例容易猜到最小的代价就是 \(n+1\),最大值最小值交替放置,即 \(n, 1, n-1, 2, n-2, 3,...\)
神奇的代码
#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)
题目大意
给定一个包含\(n\)个数的数组\(a\),问是否存在一个正整数 \(x\),使得任意 \(1 \leq i < j \leq n\),都有 \(gcd(a_i + x, a_j + x) = 1\)
解题思路
考虑怎样的\(x\)不会使得两个数互质。以下\(\%\)和 \(mod\)都是取余操作。
既然不互质,我们假设它们是公因数 \(c\) ,这意味着\(a_i \% c = a_j \% c\),且\(x = c - a_i \% c\),这样 \((a_i + x)\)和 \((a_j + x)\)才都是 \(c\)的倍数,因此这样的 \(x\)不能取。
那么如果对于一个公因数 \(c\),对它取余的范围 \([0,c-1]\)里全部数都不能取,那就说明不存在一个 \(x\)满足题目的条件。
而\(y \in [0, c-1]\) 不能取,意味着有至少两个\(a_i \% c = y\)。
因此我们枚举\(c\),统计所有 \(a_i \% c\)的取值。如果 \([0,c-1]\)中的取值出现次数都大于 \(1\),那意味着全部数都不能取,不存在此类的 \(x\)( \(x\)无论取什么, \(a_i \% c\)的结果是\(c- x \% c\) 那些\(a_i\)加上 \(x\)后会有公因数 \(c\))
因为数只有\(100\),根据鸽笼原理,我们的 \(c\)最多枚举到 \(50\)即可。更大的话肯定有 \(y \in [0, c-1]\) 出现次数小于\(2\)。
而对于一个\(c\),如果存在一个 \(y \in [0, c - 1]\) ,其出现次数为\(1\)或 \(0\),则 \(x\)可取满足 \(x \mod c = y\)的值
而对于所有的\(c\),都至少存在一个 \(y \in [0, c-1]\) ,其出现次数为\(1\)或 \(0\),那么就有若干个类似的同余方程 \(x \mod c = y\)。
因为考虑的是公因数,最终这些 \(c\)都可以归功到质数上,那就相当于我们有若干个形如\(x \equiv y \mod c\)的同余方程组成的同余方程组,各个 \(c\)之间互质(是质数),由中国剩余定理可知该方程组一定有解,且该解可以构造出来。因此也就有解。
即如果所有的 \(c\)都至少有一个 \(y\)出现次数小于 \(2\),那么就一定存在 \(x\)满足要求。
神奇的代码
#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;
}
当然,一开始的想法不是这样,我们考虑更相减损术。
即\(gcd(a_i + x, a_j + x) = gcd(a_i + x, |a_j - a_i|)\)
如果其值不为\(1\),我们假设是 \(c\),那么 \(x\)就不能取值使得 \(a_i+x\)是 \(c\)的倍数,即 \(x \neq c - a_i \% c\)。
那么我们枚举 \(c\),再枚举 \(a_i\),如果存在 \(|a_i - a_j|\)是 \(c\)的倍数,那么 \(x\)就有一个不能取的值。
当遍历完 \(a_i\)后, \(x\)不能取的值覆盖了 \([0,c-1]\),那么就不存在\(x\) 满足条件了。
否则就有一个满足条件的同余方程。
对所有\(c\)都判断一遍,如果都有能取的值,同上,根据中国剩余定理可知一定有解。
而 \(c\)的数量,根据鸽笼原理,一个 \(a_i\)至多占一个位置,因此 \(c\)枚举到 \(100\)就可以了。
下面代码复杂度比较高,因为多了个判断差值是不是\(c\)的倍数的循环,多了个\(O(n)\),事先对差值分解质因数可以降到 \(O(1)\) 。
神奇的代码
#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)
题目大意
\(A\)和 \(B\)玩游戏,有三个包含\(n\)个数的数组\(a,b,c\),然后依次对\(i \in [0, n - 1]\) 进行操作,得到数组\(d\)
- \(A\)将 \(a_i, b_i, c_i\)中的一个值丢掉
- \(B\)从剩下的两个数选一个数,作为\(d_i\)
如果最终数组\(d\)是一个排列,则 \(A\)获胜,否则 \(B\)获胜。
现在给定数组 \(a,b\),假设两人绝顶聪明,问有多少个数组\(c\),满足\(A\)存在必胜策略。
解题思路
手玩一下会发现\(A\)取走后剩下的两个数一定是相同的,即让 \(B\)取的数一定是确定的。感性原因如下:
首先,如果剩下的两个数有一个是\(B\)先前取过的,那么 \(B\)一定会取这个数,这样 \(d\)就不是一个排列, \(B\)赢了。
因此,剩下的两个数必须都是 \(B\)没取过的数,如果这两个数不一样,是\(a_i,b_i\),由于不确定 \(B\)会取谁,在之后的状态里,必须还包含 \(a_i,b_i\)这两个值,且\(c_i\)还是个未取过的数,此时\(A\)只要丢弃先前 \(B\)选的 \(a_i\)或 \(b_i\),就能保证剩下的数一定都是 \(B\)没取过的。但再之后的情况,就是我们不确定 \(a_i,b_i,c_i\)是哪一个数没被取过,我们需要对每种情况去应对,此时就几乎做不到保证每次剩下的两个数都是 \(B\)没取过的。
因此得出一个结论就是,对于 \(c_i\),如果 \(a_i==b_i\),那么 \(c_i\)任取(随后\(A\)丢掉 \(c_i\)),有 \(n\)种方式 。而如果\(a_i \neq b_i\),那么要么 \(c_i = a_i\),要么 \(c_i = b_i\), \(A\)丢掉仅出现一次的那个数,这样每次 \(B\)面对两个相同的数无从选择,只能乖乖照做。
既然知道了数组\(c\)的构造方法,那怎么判断和统计满足条件的数量呢。
首先对于\(a_i=b_i\)的那些下标,其取值肯定是 \(a_i\),且方案数是 \(n\)。
然后我们考虑 \(a_i \neq b_i\)的下标,究竟要选择 \(a_i\)还是 \(b_i\)。
一个朴素的想法就是搜索,假设选择 \(a_i\), 然后搜后续情况,再假设选\(b_i\),搜后续情况。
搜后续情况的时候,有个容易想到的技巧,就是如果我选择了 \(a_i\),那么其他包含 \(a_i\)的下标只能选另一个数了。
下标与下标之间由于 \(a_i\)和 \(b_i\)的关系构成了一张图。如果能在这张图上搜索就更好了。
考虑这张图该如何构造,无非就两种,一种是点是下标,边是数字,另一种是点是数字,边是下标。
分别考虑这两张图,后者才能完美的符合我们的要求:如果我选择了 \(a_i\),那么我要找其他包含 \(a_i\)的下标。而且前一张图的构造复杂度是\(O(n^2)\)会超时(
而如何体现我们选择了\(a_i\)呢?
因为边是下标,连接了 \(a_i\)和 \(b_i\),如果我们选择了 \(a_i\),那么我们就给该边赋予一个指向\(a_i\) 的方向,那其他与\(a_i\)的连边的方向只能背离 \(a_i\)。最后如果所有数的入度都为\(1\),那么这是个可行的方案。
那方案数怎么统计呢?
首先由于连边,我们会得到若干个连通块,各个连通块的取值的乘积就是最终方案数。
由于只有\(n\)个下标,要选出 \(n\)个不同的数,对于一个连通块而言,如果它有\(x\)个点,那意味着必须有 \(x\)条边(即 \(x\)个下标),才能达成\(x\)个下标选 \(x\)个数的条件,才有可能合法。否则:
- 如果一个连通块有\(x\)个点, \(x-1\)条边,那意味着有一个点(数)不能被选到,则\(d\)也不是一个排列
- 如果一个连通块有\(x\)个点, \(x+1\)条边,那意味着有一个点(数)会被两个下标分别选了一次,则\(d\)也不是一个排列
而对于 一个有 \(n\)个点, \(n\)条边的连通块,其实就是一棵基环树(一棵树加一条边)。考虑对这个基环树的边赋予方向的话,容易发现只有两种情况:该基环数里唯一的环的方向是顺时针还是逆时针,而其他非环边的方向都是背离环的。
注意如果是自环的话,此时自环边的顺逆没有任何区别,此时的方案数应是上面提到的\(n\),即 \(c_i\)任意取。
因此总结以上思考可以得出,如果各个连通块均满足点数等于边数,则存在数组\(c\)。而有自环的连通块贡献答案\(n\),无自环的贡献 \(2\),答案就是这些数的累乘。
假设有自环的连通块数量是\(cnt1\),无自环的是 \(cnt2\),那答案就是 \(n^{cnt1} \times 2^{cnt2}\)
而由于一条边贡献了两个点的度数,因此点数等于边数的条件可以转换为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)
题目大意
给定一棵包含\(n\)个节点的树,第\(i\)条边连接了节点 \(x_i, y_i\)。有\(k\)只蝴蝶在树的节点上,第\(i\)只蝴蝶在节点 \(a_i\)上。一个节点至多有一只蝴蝶。
依次进行以下操作:
- 依次对于边\(i = 1,2,3,...,n-1\),等概率为该边赋一个方向
- 依次对于边\(i = 1,2,3,...,n-1\),如果边的起始点有蝴蝶,且终点没有蝴蝶,则该蝴蝶会飞到终点上
最后,等概率选择两只蝴蝶,计算它们的距离,即边数。
问该边数的期望值。
解题思路
虽然初看这题感觉是神仙期望题,但按照套路来还是不难。
由期望的线性可加性,且所有情况都是等概率的,既然是边数,我们就考虑每条边对期望的贡献。
如果仅仅是选择两个蝴蝶问它们的期望距离,对于一条边来说,它对答案的贡献次数就是\(sz_v * (k - sz_v)\) ,其中\(sz_v\)就是该边深度更深的节点的子树的蝴蝶数。
如果多了移动操作,那么 \(sz_v\)的值就不是固定了,而是有概率的。
而边对答案的贡献次数就变成了期望次数(?存疑,还在思考
神奇的代码
F. Koxia and Sequence (CF 1770 F)
题目大意
<++>
解题思路
<++>
神奇的代码
G. Koxia and Bracket (CF 1770 G)
题目大意
<++>
解题思路
<++>
神奇的代码
H. Koxia, Mahiru and Winter Festival (CF 1770 H)
题目大意
<++>
解题思路
<++>
神奇的代码