2022-12-31 17:54阅读: 171评论: 0推荐: 0

Good Bye 2022: 2023 is NEAR

A. Koxia and Whiteboards (CF 1770 A)

题目大意

给定一个包含n个数的数组 a,以及 m次操作。

i次操作将 a 中的一个数替换为bi

m次操作后数组 a的和的最大值。

解题思路

直接贪心,每次选最小的数替换称bi即可。

用优先队列维护最小值。

神奇的代码
#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的排列的价值为

max0ink(maxiji+k1aj+miniji+k1aj)

请构造一个排列,是其价值最小。

解题思路

k等于 1时,无论怎么构造代价都是 2n

k更大时,看样例容易猜到最小的代价就是 n+1,最大值最小值交替放置,即 n,1,n1,2,n2,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,使得任意 1i<jn,都有 gcd(ai+x,aj+x)=1

解题思路

考虑怎样的x不会使得两个数互质。以下%mod都是取余操作。

既然不互质,我们假设它们是公因数 c ,这意味着ai%c=aj%c,且x=cai%c,这样 (ai+x)(aj+x)才都是 c的倍数,因此这样的 x不能取。

那么如果对于一个公因数 c,对它取余的范围 [0,c1]里全部数都不能取,那就说明不存在一个 x满足题目的条件。

y[0,c1] 不能取,意味着有至少两个ai%c=y

因此我们枚举c,统计所有 ai%c的取值。如果 [0,c1]中的取值出现次数都大于 1,那意味着全部数都不能取,不存在此类的 xx无论取什么, ai%c的结果是cx%c 那些ai加上 x后会有公因数 c

因为数只有100,根据鸽笼原理,我们的 c最多枚举到 50即可。更大的话肯定有 y[0,c1] 出现次数小于2

而对于一个c,如果存在一个 y[0,c1] ,其出现次数为10,则 x可取满足 xmodc=y的值

而对于所有的c,都至少存在一个 y[0,c1] ,其出现次数为10,那么就有若干个类似的同余方程 xmodc=y

因为考虑的是公因数,最终这些 c都可以归功到质数上,那就相当于我们有若干个形如xymodc的同余方程组成的同余方程组,各个 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(ai+x,aj+x)=gcd(ai+x,|ajai|)

如果其值不为1,我们假设是 c,那么 x就不能取值使得 ai+xc的倍数,即 xcai%c

那么我们枚举 c,再枚举 ai,如果存在 |aiaj|c的倍数,那么 x就有一个不能取的值。

当遍历完 ai后, x不能取的值覆盖了 [0,c1],那么就不存在x 满足条件了。

否则就有一个满足条件的同余方程。

对所有c都判断一遍,如果都有能取的值,同上,根据中国剩余定理可知一定有解。

c的数量,根据鸽笼原理,一个 ai至多占一个位置,因此 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)

题目大意

AB玩游戏,有三个包含n个数的数组a,b,c,然后依次对i[0,n1] 进行操作,得到数组d

  • Aai,bi,ci中的一个值丢掉
  • B从剩下的两个数选一个数,作为di

如果最终数组d是一个排列,则 A获胜,否则 B获胜。

现在给定数组 a,b,假设两人绝顶聪明,问有多少个数组c,满足A存在必胜策略。

解题思路

手玩一下会发现A取走后剩下的两个数一定是相同的,即让 B取的数一定是确定的。感性原因如下:

首先,如果剩下的两个数有一个是B先前取过的,那么 B一定会取这个数,这样 d就不是一个排列, B赢了。

因此,剩下的两个数必须都是 B没取过的数,如果这两个数不一样,是ai,bi,由于不确定 B会取谁,在之后的状态里,必须还包含 ai,bi这两个值,且ci还是个未取过的数,此时A只要丢弃先前 B选的 aibi,就能保证剩下的数一定都是 B没取过的。但再之后的情况,就是我们不确定 ai,bi,ci是哪一个数没被取过,我们需要对每种情况去应对,此时就几乎做不到保证每次剩下的两个数都是 B没取过的。

因此得出一个结论就是,对于 ci,如果 ai==bi,那么 ci任取(随后A丢掉 ci),有 n种方式 。而如果aibi,那么要么 ci=ai,要么 ci=biA丢掉仅出现一次的那个数,这样每次 B面对两个相同的数无从选择,只能乖乖照做。

既然知道了数组c的构造方法,那怎么判断统计满足条件的数量呢。

首先对于ai=bi的那些下标,其取值肯定是 ai,且方案数是 n

然后我们考虑 aibi的下标,究竟要选择 ai还是 bi

一个朴素的想法就是搜索,假设选择 ai, 然后搜后续情况,再假设选bi,搜后续情况。

搜后续情况的时候,有个容易想到的技巧,就是如果我选择了 ai,那么其他包含 ai的下标只能选另一个数了。

下标与下标之间由于 aibi的关系构成了一张图。如果能在这张图上搜索就更好了。

考虑这张图该如何构造,无非就两种,一种是是下标,是数字,另一种是是数字,是下标。

分别考虑这两张图,后者才能完美的符合我们的要求:如果我选择了 ai,那么我要找其他包含 ai的下标。而且前一张图的构造复杂度是O(n2)会超时(

而如何体现我们选择了ai呢?

因为是下标,连接了 aibi,如果我们选择了 ai,那么我们就给该边赋予一个指向ai 的方向,那其他与ai的连边的方向只能背离 ai。最后如果所有数的入度都为1,那么这是个可行的方案。

那方案数怎么统计呢?

首先由于连边,我们会得到若干个连通块,各个连通块的取值的乘积就是最终方案数。

由于只有n个下标,要选出 n个不同的数,对于一个连通块而言,如果它有x个点,那意味着必须有 x条边(即 x个下标),才能达成x个下标选 x个数的条件,才有可能合法。否则:

  • 如果一个连通块有x个点, x1条边,那意味着有一个点(数)不能被选到,则d也不是一个排列
  • 如果一个连通块有x个点, x+1条边,那意味着有一个点(数)会被两个下标分别选了一次,则d也不是一个排列

而对于 一个有 n个点, n条边的连通块,其实就是一棵基环树(一棵树加一条边)。考虑对这个基环树的边赋予方向的话,容易发现只有两种情况:该基环数里唯一的环的方向是顺时针还是逆时针,而其他非环边的方向都是背离环的。

注意如果是自环的话,此时自环边的顺逆没有任何区别,此时的方案数应是上面提到的n,即 ci任意取。

因此总结以上思考可以得出,如果各个连通块均满足点数等于边数,则存在数组c。而有自环的连通块贡献答案n,无自环的贡献 2,答案就是这些数的累乘。

假设有自环的连通块数量是cnt1,无自环的是 cnt2,那答案就是 ncnt1×2cnt2

而由于一条边贡献了两个点的度数,因此点数等于边数的条件可以转换为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条边连接了节点 xi,yi。有k只蝴蝶在树的节点上,第i只蝴蝶在节点 ai上。一个节点至多有一只蝴蝶。

依次进行以下操作:

  • 依次对于边i=1,2,3,...,n1,等概率为该边赋一个方向
  • 依次对于边i=1,2,3,...,n1,如果边的起始点有蝴蝶,且终点没有蝴蝶,则该蝴蝶会飞到终点上

最后,等概率选择两只蝴蝶,计算它们的距离,即边数。

问该边数的期望值。

解题思路

虽然初看这题感觉是神仙期望题,但按照套路来还是不难。

由期望的线性可加性,且所有情况都是等概率的,既然是边数,我们就考虑每条边对期望的贡献。

如果仅仅是选择两个蝴蝶问它们的期望距离,对于一条边来说,它对答案的贡献次数就是szv(kszv) ,其中szv就是该边深度更深的节点的子树的蝴蝶数。

如果多了移动操作,那么 szv的值就不是固定了,而是有概率的。

而边对答案的贡献次数就变成了期望次数(?存疑,还在思考

神奇的代码


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 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(171)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.