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\times b\le 3\times \operatorname{gcd}^2(a,b) \]

\(a=mp,b=np(m,n\in N^*,\gcd(m,n)=1)\),则上式可转化为

\[mnp^2\le 3p^2\Longrightarrow mn\le 3 \]

于是\((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)\)转化一下,得

\[\sum\operatorname{lcm}(c,\gcd(a,b))=\sum\frac{c\times \gcd(a,b)}{\gcd(a,b,c)}=\sum_{c=1}^{n-2}c(\sum\frac{\gcd(a,b)}{\gcd(a,b,c)}) \]

由于\(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)\)

posted @ 2022-09-04 14:23  verjun  阅读(65)  评论(0编辑  收藏  举报