Codeforces Round #818 (Div. 2) CF1717 解题报告
Codeforces Round #818 (Div. 2) CF1717 解题报告
A
Description
求出满足\(1\le a,b\le N,\frac{\operatorname{lcm}(a,b)}{\gcd(a,b)}\le 3\)的二元组\((a,b)\)的数目。
\(N\le 10^8\)
Sol
由\(a\times b=\operatorname{lcm}(a,b)\times \gcd(a,b)\)转化上述分式,可得
设\(a=mp,b=np(m,n\in N^*,\gcd(m,n)=1)\),则上式可转化为
于是\((m,n)\)只有\((1,1),(1,2),(2,1),(3,1),(1,3)\)这\(5\)种情况。
最终答案即为\(N+\lfloor\frac N2\rfloor\times 2+\lfloor\frac N3\rfloor\times 2\)
Code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
signed main() {
int T = Read();
while(T--) {
int x = Read();
printf("%lld\n", x + (x / 2) * 2 + (x / 3) * 2);
}
return 0;
}
B
Description
构造一个\(n\times n\)的包含\(.,X\)的矩阵,要求每个\(1\times k,k\times 1\)的子矩阵内至少有一个\(X\)且位置\((a,b)\)为\(X\)且\(X\)的数量最小,保证\(n\)为\(k\)的倍数。
\(1\le k,a,b\le n\le 500\)
Sol
首先可以发现,由于\(n\)为\(k\)的倍数,所以构造出来的\(X\)最少的矩阵一定是每个\(1\times k, k\times 1\)的子矩阵内恰好有一个\(1\),第一行形如\(X..X..X..\),后面的行由第一行平移得出。
找到一个合法矩阵后直接把矩阵从某一个有\(X\)的位置平移到\((a,b)\)即可。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int mp[505][505];
signed main() {
int T = Read();
while(T--) {
memset(mp, 0, sizeof(mp));
int n = Read(), k = Read(), r = Read(), c = Read();
for(int i = 1; i <= n; i++)
for(int j = (i - 1) % k + 1; j <= n; j += k) mp[i][j] = 1;
int flag = 0;
for(int i = 1; i <= n; i++) if(mp[i][c]) {flag = i; break ;}
int num = r - flag;
for(int i = 1; i <= n; i++) {
int nw = (i - num + n - 1) % n + 1;
for(int j = 1; j <= n; j++) putchar(mp[nw][j] ? 'X' : '.');
puts("");
}
}
return 0;
}
C
Description
给定两个长为\(n\)的数列\(\{a\},\{b\}\),定义一次操作为选择一个数\(i\),若\(i<n\)且\(a_i\le a_{i+1}\)或\(i=n\)且\(a_i\le a_1\),则令\(a_i=a_i+1\).
问能否在若干次操作后把\(\{a\}\)变为\(\{b\}\)
\(n\le 2\times 10^5\)
Sol
由于\(a_i\)只增不减,所以当\(a_i>b_i\)时直接无解。
又由于\(a_i\le a_{i+1}\)时才能增加,即增加后\(a_i\le a_{i+1}+1\),故当\(b_{i}>b_{i+1}+1\)且\(a_i\not =b_i\)时也无解。
其余情况均有解,可以构造如下:
取\(tmp=\min{\{b_i\}}\),将所有\(a_i\)变为\(\min\{a_i,b_i,tmp\}\),易证这是可以做到的。
然后将\(tmp+1\),将所有\(a_i\)变为\(\min\{a_i,b_i,tmp\}\)
重复上一步操作直到\(tmp=\max\{b_i\}\)。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, a[200005], b[200005];
signed main() {
int T = Read();
while(T--) {
n = Read();
for(int i = 1; i <= n; i++) a[i] = Read();
for(int i = 1; i <= n; i++) b[i] = Read();
int flag = 1;
for(int i = 1; i <= n; i++) {
if(a[i] > b[i]) flag = 0;
int nxt = ((i == n) ? 1 : i + 1);
if(b[i] > b[nxt] + 1 && a[i] != b[i]) flag = 0;
}
puts(flag ? "YES" : "NO");
}
return 0;
}
D
Description
有\(2^n\)个人打淘汰赛,共\(n\)轮,\(A\)可以决定每一轮中每一对比赛者谁获胜,\(B\)可以在\(A\)决定完毕后更改至多\(k\)对比赛者的获胜情况,问\(A,B\)都进行最优操作下最终获胜者的最大编号。
\(n\le 10^5,k\le \min\{2^n-1,10^9\}\)
Sol
首先可以观察到,如果\(B\)想让一个人赢,那么\(B\)只需要调整有关他的比赛且他在该比赛中失败的场次。
换言之,如果要让\(C\)赢,那么就要从比赛的完全二叉树底代表\(C\)的点开始,一层一层的往上跳,如果该层为失败,则调整。
将一个点到树根路线上的输赢看做一个\(01\)串(称为输赢串),输为\(0\),赢为\(1\),那么调整次数即为\(0\)的个数。
\(A\)要保证\(B\)调整后所能得到的最大编号最小,那么\(A\)只能把编号大的数置于输赢串中\(0\)尽量多的位置,同样的,\(B\)也只能选择输赢串中的\(0\)个数\(\le k(\)等价于\(=k\),因为\(A\)肯定不会把大的放前面\()\)中编号最大的人。
考虑\(0\)个数为\(x\)的串有多少个,答案即为\(C_{n}^x\),组合的意义很明显。
于是\(B\)挑的人即为\(C_n^0+C_n^1+...+C_n^k\),当\(k\ge n\)时为\(2^n\)。
Code
点击查看代码
#include<bits/stdc++.h>
#define Mod 1000000007
#define int long long
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int qpow(int x, int k) {
int res = 1;
while(k) {
if(k & 1) res = res * x % Mod;
x = x * x % Mod;
k >>= 1;
}
return res;
}
int fac[100005], finv[100005], inv[100005];
int C(int n, int m) {
return fac[n] * finv[m] % Mod * finv[n - m] % Mod;
}
signed main() {
fac[0] = fac[1] = inv[0] = inv[1] = finv[0] = finv[1] = 1;
for(int i = 2; i <= 100000; i++) {
fac[i] = fac[i - 1] * i % Mod;
inv[i] = (Mod - Mod / i) * inv[Mod % i] % Mod;
finv[i] = finv[i - 1] * inv[i] % Mod;
}
int n = Read(), k = Read();
if(k >= n) {
printf("%lld\n", qpow(2, n));
return 0;
}
int sum = 0;
for(int i = 0; i <= k; i++) sum = (sum + C(n, i)) % Mod;
cout << sum << endl;
return 0;
}
E
Description
对于所有满足\(a+b+c=n\)的三元组\((a,b,c)\)求出\(\sum\operatorname{lcm}(c,\gcd(a,b))\)。
\(3\le n\le 10^5\)
Sol
稍微用\(a\times b=\operatorname{lcm}(a,b)\times \gcd(a,b)\)转化一下,得
由于\(a+b=n-c\),所以\(\gcd(a,b)\)只可能是\(n-c\)的约数,为\(O(2\sqrt{n-c})\)级别(实际上远远达不到),所以枚举\(c\)和\(\gcd(a,b)\)即可做到快速计算。
然后当\(\gcd(a,b)=p\)时不能算入诸如\(a=2p,b=4p\)的\((a,b)\),容斥一下减掉即可。
Code
点击查看代码
#include<bits/stdc++.h>
#define Mod 1000000007
#define int long long
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, f[100005], mu[100005], prime[100005], flag[100005], cnt;
vector<int> ys[100005];
void sieve(){
mu[1] = 1;
for(int i = 2; i <= 100000; i++){
if(!flag[i]) prime[++cnt] = i, mu[i] = -1;
for(int j = 1; j <= cnt && i * prime[j] <= 100000; j++){
flag[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
mu[i * prime[j]] -= mu[i];
}
}
}
int gcd(int x, int y) {
return (x % y == 0) ? y : gcd(y, x % y);
}
signed main() {
sieve();
for(int i = 1; i <= 100000; i++)
for(int j = i; j <= 100000; j += i) ys[j].push_back(i);
for(int i = 1; i <= 100000; i++) {
for(int j = 0; j < ys[i].size(); j++)
f[i] += mu[ys[i][j]] * ((i / ys[i][j]) - 1);
}
n = Read();
int sum = 0;
for(int i = 1; i <= n - 2; i++) {
int res = n - i;
for(int j = 0; j < ys[res].size(); j++) {
int val = ys[res][j] / gcd(ys[res][j], i);
int num = f[res / ys[res][j]];
sum = (sum + i * val % Mod * num % Mod) % Mod;
}
}
cout << sum << endl;
return 0;
}
F
Description
给定三个长为\(n\)的数列\(\{a\},\{b\},\{s\}\)与\(m\)组操作\((u_i,v_i)\),\(\{b\}\)初始全为\(0\)。
对于每一组操作,你可以选择令\(b_{u_i}=b_{u_i}+1,b_{v_i}=b_{v_i}-1\)或\(b_{u_i}=b_{u_i}-1,b_{v_i}=b_{v_i}+1\)。
问执行完这\(m\)个操作后能否使对于所有的\(s_i=1\)满足\(a_i=b_i\),若能则输出具体操作。
Sol
将问题转化到图上,则问题变为给一张无向图的边定向,使一些指定点的入度减去出度为一个给定的值。
如果不是每个边都必须用到,那么就是一个经典的网络流问题。
但现在每条边都要用到,我们可以改变建图方式来强行让每条能用的边都用上。
先化边为点,我们从虚拟源点向每条边引出一条流量为\(1\)的边,由于我们跑的是最大流,所以如果一条边能用必然会被用上。
然后对于每条边向\(u_i,v_i\)分别连容量为\(1\)的边,流过一条边就代表对应点的权值加\(1\)
现在我们没有讨论\(-1\)的情况,而我们需要将边向点连出的边没流的变为\(-1\),设一共有\(du_i\)条边连接了\(i\),其中流了\(x\)条边进\(i\),那么需要满足\(x-(du_i-x)=a_i\),即\(x=\frac{du_i+a_i}2\),然后从每个点向虚拟汇点连接一条容量为\(x\)的边。
如果\(s_i=0\),那么没有要求,向汇点连一条流量为\(+\infin\)的边即可。
不过最大流可能不唯一,我们需要保证\(s_i=1\)的边优先被流满,所以先不连\(s_i=0\)的边跑一遍网络流,再连上跑一遍,如果所有\(s_i=1\)的点到汇点的边流满且最大流\(=m\),那么就成立,反正不成立。
输出操作的时候直接看一条边\((i,u_i)\)是否被流,是则输出\((v_i,u_i)\),反之输出\((u_i,v_i)\)