【CodeForces】Codeforces Round 614

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. NEKO’s Maze Game

注意到保证了 ( 1 , 1 ) (1,1) ( 2 , n ) (2,n) 始终不会出现障碍,
能够从 ( 1 , 1 ) (1,1) 到达 ( 2 , n ) (2,n) 当且仅当没有出现路径被隔断的情况。

那么,维护隔断路径的点对的数量 A n s Ans ,每次修改后 O ( 1 ) O(1) 计算 A n s Ans 的增量即可。

时间复杂度 O ( Q ) O(Q)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
bool a[2][MAXN];
int main() {
	int n, m; read(n), read(m);
	int ans = 0;
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y), x--;
		if (a[x][y]) {
			a[x][y] = false;
			for (int j = -1; j <= 1; j++)
				ans -= a[x ^ 1][y + j];
		} else {
			a[x][y] = true;
			for (int j = -1; j <= 1; j++)
				ans += a[x ^ 1][y + j];
		}
		if (ans) puts("No");
		else puts("Yes");
	}
	return 0;
}

Problem B. Aroma’s Search

注意到给定的坐标序列两维坐标是分别单调的,可以认为我们能够访问的坐标编号是一个区间。

枚举这个区间 [ l , r ] [l,r] ,以及 l , r l,r 中较早被访问的点,判断是否可行即可。

时间复杂度 O ( L o g 2 T ) O(Log^2T)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
ll x[MAXN], y[MAXN], ax, ay, bx, by, sx, sy, t;
ll absll(ll x) {
	if (x < 0) return -x;
	else return x;
}
int main() {
	int n = 1; read(x[1]), read(y[1]);
	read(ax), read(ay), read(bx), read(by);
	read(sx), read(sy), read(t);
	while (x[n] <= sx + t && y[n] <= sy + t) {
		n++;
		x[n] = ax * x[n - 1] + bx;
		y[n] = ay * y[n - 1] + by;
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	for (int j = i; j <= n; j++) {
		ll dis = absll(x[i] - x[j]) + absll(y[i] - y[j]);
		ll lft = t - dis;
		if (lft >= 0) {
			if (absll(sx - x[i]) + absll(sy - y[i]) <= lft) chkmax(ans, j - i + 1);
			if (absll(sx - x[j]) + absll(sy - y[j]) <= lft) chkmax(ans, j - i + 1);
		}
	}
	cout << ans << endl;
	return 0;
}

Problem C. Xenon’s Attack on the Gangs

考虑从小到大填入权值,则每次填入的权值要想对 S S 有贡献,就必须与之前填入的所有权值在同一条路径 P P 上,此时产生的贡献为包含 P P 的路径数。

那么,显然每次在 P P 的两段填入新的权值是更优的。

枚举最后一次产生贡献的填入后的 P P ,简单 DP 即可。

扫描二维码关注公众号,回复: 10184545 查看本文章

时间复杂度 O ( N 2 ) O(N^2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3005;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, size[MAXN][MAXN], nxt[MAXN][MAXN], len[MAXN][MAXN];
int x[MAXN], y[MAXN]; ll dp[MAXN][MAXN];
vector <int> a[MAXN];
void dfs(int root, int fa, int pos) {
	size[root][pos] = 1;
	len[root][pos] = len[root][fa] + 1;
	if (fa == root) nxt[root][pos] = pos;
	else nxt[root][pos] = nxt[root][fa];
	for (auto x : a[pos])
		if (x != fa) {
			dfs(root, pos, x);
			size[root][pos] += size[root][x];
		}
}
ll getans(int x, int y) {
	if (x == y) return 0;
	if (dp[x][y] != -1) return dp[x][y];
	return dp[x][y] = (n - size[x][nxt[x][y]]) * (n - size[y][nxt[y][x]]) + max(getans(nxt[x][y], y), getans(x, nxt[y][x]));
}
int main() {
	read(n);
	for (int i = 1; i <= n - 1; i++) {
		read(x[i]), read(y[i]);
		a[x[i]].push_back(y[i]);
		a[y[i]].push_back(x[i]);
	}
	for (int i = 1; i <= n; i++) {
		size[i][i] = n;
		for (auto x : a[i])
			dfs(i, i, x);
	}
	memset(dp, -1, sizeof(dp));
	ll ans = 0;
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		chkmax(ans, getans(i, j));
	cout << ans << endl;
	return 0;
}

Problem D. Chaotic V.

可能涉及到的点只有 K = 5000 K=5000 个,若能建立它们的虚树,则可以转化成一个图论问题。

为了建立虚树,我们需要能够计算两数的 Lca ,以及比较两数的深度。筛出 K K 以内的质数,用因子分解的形式表示一个数,不难达成以上要求。

建立完虚树后,将关键点选在树的一个权值重心即可。

时间复杂度 O ( K 2 L o g K + N ) O(\frac{K^2}{LogK}+N)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int tot, prime[MAXN], f[MAXN], home[MAXN];
void sieve(int n) {
	for (int i = 2; i <= n; i++) {
		if (f[i] == 0) {
			prime[++tot] = f[i] = i;
			home[i] = tot;
		}
		for (int j = 1; j <= tot && prime[j] <= f[i]; j++) {
			int tmp = prime[j] * i;
			if (tmp > n) break;
			f[tmp] = prime[j];
		}
	}
}
int m; vector <int> num[MAXN];
int dist(vector <int> a, vector <int> b) {
	int ans = 0;
	for (int i = 1; i <= tot; i++)
		ans += abs(a[i] - b[i]);
	return ans;
}
vector <int> lca(vector <int> a, vector <int> b) {
	for (int i = tot; i >= 1; i--) {
		if (a[i] != b[i]) {
			chkmin(a[i], b[i]);
			for (int j = i - 1; j >= 1; j--)
				a[j] = 0;
			return a;
		}
	}
	return a;
}
vector <pair <int, int>> a[MAXN];
int q, root, cnt[MAXN], sum[MAXN]; ll ans;
void addedge(int x, int y) {
	int d = dist(num[x], num[y]);
	a[x].emplace_back(y, d);
	a[y].emplace_back(x, d);
}
void dfs(int pos, int fa) {
	sum[pos] = cnt[pos];
	for (auto x : a[pos])
		if (x.first != fa) {
			dfs(x.first, pos);
			sum[pos] += sum[x.first];
		}
}
void getroot(int pos, int fa) {
	root = pos;
	for (auto x : a[pos])
		if (x.first != fa && sum[x.first] * 2 > q) {
			getroot(x.first, pos);
			return;
		}
}
void getans(int pos, int fa, int len) {
	ans += 1ll * len * cnt[pos];
	for (auto x : a[pos])
		if (x.first != fa) getans(x.first, pos, len + x.second);
}
int main() {
	int n = 5000; m = n, sieve(n);
	num[0].resize(tot + 1);
	for (int i = 1; i <= n; i++) {
		num[i] = num[i - 1];
		int tmp = i;
		while (tmp != 1) {
			num[i][home[f[tmp]]]++;
			tmp /= f[tmp];
		}
	}
	static int Stack[MAXN];
	int top = 1; Stack[top = 1] = 1;
	for (int i = 2; i <= n; i++) {
		vector <int> tmp = lca(num[i], num[Stack[top]]);
		if (tmp == num[Stack[top]]) Stack[++top] = i;
		else {
			while (tmp < num[Stack[top - 1]]) {
				addedge(Stack[top], Stack[top - 1]);
				top--;
			}
			if (tmp == num[Stack[top - 1]]) {
				addedge(Stack[top], Stack[top - 1]);
				Stack[top] = i;
			} else {
				num[++m] = tmp;
				addedge(Stack[top], m);
				Stack[top] = m;
				Stack[++top] = i;
			}
		}
	}
	for (int i = 1; i <= top - 1; i++)
		addedge(Stack[i], Stack[i + 1]);
	read(q);
	for (int i = 1; i <= q; i++) {
		int x; read(x);
		chkmax(x, 1);
		cnt[x]++;
	}
	dfs(1, 0);
	getroot(1, 0);
	getans(root, 0, 0);
	cout << ans << endl;
	return 0;
}

Problem E. Rin and The Unknown Flower

考虑首先询问 C O , C H , H O , H C CO,CH,HO,HC ,此时的花费为 1 1

此后,对于没有确定的位置,有如下可能:
1 1 、连续两个以 O O 开头的字符
2 2 、连续两个相同的字符

分如下两种情况讨论。

( 1 ) (1) 、没有确定任何一个字符
此时,字符串应当有连续的若干个 O O 开头,并且剩余的位置均为 C , H C,H 中的一种。
询问 C C C , H H H CCC,HHH ,若确定了出现位置,则剩下没有被确定的位置均为 O O
否则,询问 O O O OOO ,若确定了出现位置,则再花费一次询问判断剩余的位置上是 C C 还是 H H
否则,原串一定是 O O C C , O O H H OOCC,OOHH 中的一种,花费一次询问判断即可。

N = 4 N=4 时有最大花费 1 + 3 9 + 1 16 < 1.4 1+\frac{3}{9}+\frac{1}{16}<1.4

( 2 ) (2) 、确定了某些字符
找到最靠右的确定的位置 x x ,令 [ l , r ] = [ x , x ] [l,r]=[x,x]
l 1 l-1 上的字符已被确定,则令 l = l 1 l=l-1
否则,若 s l = C s_l=C l 1 l-1 上的字符应为 C , O C,O 中的一种, 花费一次询问判断;
否则,若 s l = H s_l=H l 1 l-1 上的字符应为 H , O H,O 中的一种, 花费一次询问判断;
否则,即 s l = O s_l=O l 1 l-1 上的字符一定是 O O

此时,我们确定了原串的一个前缀 [ 1 , r ] [1,r]
s r = C s_r=C ,则 r 1 r-1 上的字符一定是 C C
否则,若 s r = H s_r=H ,则 r 1 r-1 上的字符一定是 H H
否则,即 s r = O s_r=O ,首先花费一次询问判断 r 1 r-1 上的字符是否是 O O ,若不是,则可以再花费一次询问确定整个字符串。

最大花费为
m a x i [ 4 , 50 ] { 1 + j = 3 i 1 j 2 + 1 i 2 } < 1.4 max_{i\in[4,50]}\{1+\sum_{j=3}^{i}\frac{1}{j^2}+\frac{1}{i^2}\}<1.4

单组数据时间复杂度 O ( N 2 ) O(N^2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
char s[MAXN]; int n, k;
void answer(int n) {
	cout << '!' << ' ';
	for (int i = 1; i <= n; i++)
		cout << s[i];
	cout << endl;
}
bool get(int v) {
	read(k); if (k == -1) exit(0);
	bool ans = false;
	while (k--) {
		int x; read(x);
		if (v == x) ans = true;
	}
	return ans;
}
int main() {
	int T; read(T);
	while (T--) {
		read(n);
		for (int i = 1; i <= n; i++)
			s[i] = 0;
		cout << '?' << ' ' << "CH" << endl;
		read(k); if (k == -1) exit(0);
		while (k--) {
			int x; read(x);
			s[x] = 'C', s[x + 1] = 'H';
		}
		cout << '?' << ' ' << "CO" << endl;
		read(k); if (k == -1) exit(0);
		while (k--) {
			int x; read(x);
			s[x] = 'C', s[x + 1] = 'O';
		}
		cout << '?' << ' ' << "HO" << endl;
		read(k); if (k == -1) exit(0);
		while (k--) {
			int x; read(x);
			s[x] = 'H', s[x + 1] = 'O';
		}
		cout << '?' << ' ' << "HC" << endl;
		read(k); if (k == -1) exit(0);
		while (k--) {
			int x; read(x);
			s[x] = 'H', s[x + 1] = 'C';
		}
		int l = 0, r = 0;
		for (int i = 1; i <= n; i++)
			if (s[i]) l = r = i;
		if (l == 0) {
			cout << '?' << ' ' << "CCC" << endl;
			read(k); if (k == -1) exit(0);
			while (k--) {
				int x; read(x);
				s[x] = s[x + 1] = s[x + 2] = 'C';
			}
			cout << '?' << ' ' << "HHH" << endl;
			read(k); if (k == -1) exit(0);
			while (k--) {
				int x; read(x);
				s[x] = s[x + 1] = s[x + 2] = 'H';
			}
			if (s[n]) {
				for (int i = 1; i <= n; i++)
					if (s[i] == 0) s[i] = 'O';
			} else {
				cout << '?' << ' ' << "OOO" << endl;
				read(k); if (k == -1) exit(0);
				while (k--) {
					int x; read(x);
					s[x] = s[x + 1] = s[x + 2] = 'O';
				}
				if (s[1]) {
					for (int i = 1; i <= n; i++)
						if (s[i] == 0) s[i] = 'C';
					cout << '?' << ' ';
					for (int i = 1; i <= n; i++)
						cout << s[i];
					cout << endl;
					if (!get(1)) {
						for (int i = 1; i <= n; i++)
							if (s[i] == 'C') s[i] = 'H';
					}
				} else {
					assert(n == 4);
					cout << "? OOCC" << endl;
					if (get(1)) s[1] = s[2] = 'O', s[3] = s[4] = 'C';
					else s[1] = s[2] = 'O', s[3] = s[4] = 'H';
				}
			}
		} else {
			while (l != 1 || r != n) {
				if (l > 1 && s[l - 1]) l--;
				else if (l > 1) {
					if (s[l] == 'C') {
						cout << '?' << ' ' << 'O';
						for (int i = l; i <= r; i++)
							cout << s[i];
						cout << endl;
						if (get(l - 1)) s[l - 1] = 'O';
						else s[l - 1] = 'C';
					} else if (s[l] == 'H') {
						cout << '?' << ' ' << 'O';
						for (int i = l; i <= r; i++)
							cout << s[i];
						cout << endl;
						if (get(l - 1)) s[l - 1] = 'O';
						else s[l - 1] = 'H';
					} else s[l - 1] = 'O';
					l--;
				} else {
					if (s[r] == 'O') {
						cout << '?' << ' ';
						for (int i = l; i <= r; i++)
							cout << s[i];
						cout << 'O' << endl;
						if (get(l)) s[r + 1] = 'O';
						else {
							cout << '?' << ' ';
							for (int i = l; i <= r; i++)
								cout << s[i];
							cout << 'C' << endl;
							if (get(l)) s[r + 1] = 'C';
							else s[r + 1] = 'H';
						}
					} else s[r + 1] = s[r];
					r++;
				}
			}
		}
		answer(n);
		int res; read(res);
		if (!res) exit(0);
	}
	return 0;
}

Problem F. Nora’s Toy Boxes

定义本源的数字是集合中不存在非平凡因数,且存在非平凡倍数的数字。

对于操作 ( i , j , k ) (i,j,k) ,我们不妨认为 a i a_i 必须是本源的数字。

又因为本源的数字无法被删除,我们可以认为与一次操作相关的只有 a j , a k a_j,a_k 两个数,对于可以通过一次操作删除其一的两个数 a j , a k a_j,a_k ,在它们之间连一条边,则显然,各个联通块的答案是独立的,可以用隔板法合并。

考虑如何对删除序列计数,对于一个连通块,显然最终至少会留下一个数。

将删除的过程倒转,考虑由留下的这个数向外拓展的过程,则在状态中计入当前的点数 c n t cnt ,以及这些点所覆盖的本源的数字集合 m a s k mask ,即可转移。

时间复杂度 O ( 2 M × N 2 ) O(2^M\times N^2) ,其中 M M 表示本源的数字个数,有 M 15 M\leq 15

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 65;
const int MAXS = 32768;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, m, bit[MAXN], a[MAXN], key[MAXN];
int f[MAXN], binom[MAXN][MAXN], dp[MAXS][MAXN];
int find(int x) {
	if (f[x] == x) return x;
	else return f[x] = find(f[x]);
}
int getcnt(int x) {
	int ans = -1;
	for (int i = 1; i <= n; i++)
		if (f[i] == x) ans += key[i] == -1;
	return max(ans, 0);
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int getans(int x) {
	int cnt = getcnt(x), m = 0;
	if (cnt == 0) return 1; cnt++;
	static int ins[MAXS], type[MAXN];
	for (int i = 1; i <= n; i++)
		if (f[i] == x && key[i] != -1) key[i] = ++m;
	for (int i = 0; i < (1 << m); i++)
		ins[i] = 0;
	for (int i = 1; i <= n; i++)
		if (f[i] == x && key[i] == -1) {
			type[i] = 0;
			for (int j = 1; j <= n; j++)
				if (f[j] == x && key[j] != -1 && a[i] % a[j] == 0) type[i] |= 1 << (key[j] - 1);
			for (int j = 0; j < (1 << m); j++)
				ins[j] += (type[i] | j) == j;
		}
	static int dp[MAXN][MAXS];
	memset(dp, 0, sizeof(dp));
	for (int i = 1; i <= n; i++)
		if (f[i] == x && key[i] == -1) update(dp[1][type[i]], 1);
	for (int i = 1; i <= cnt - 1; i++)
	for (int j = 0; j < (1 << m); j++) {
		int tmp = dp[i][j];
		if (!tmp) continue;
		update(dp[i + 1][j], 1ll * tmp * (ins[j] - i) % P);
		for (int k = 1; k <= n; k++)
			if (f[k] == x && key[k] == -1 && (type[k] & j) != 0 && (type[k] | j) != j) update(dp[i + 1][j | type[k]], tmp);
	}
	return dp[cnt][(1 << m) - 1];
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		read(a[i]), f[i] = i;
	for (int i = 0; i <= n; i++) {
		binom[i][0] = 1;
		for (int j = 1; j <= i; j++)
			binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % P;
	}
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++) {
		if (key[i] == -1) continue;
		bool vis = false;
		for (int j = i + 1; j <= n; j++)
			if (a[j] % a[i] == 0) {
				vis = true;
				key[j] = -1;
				f[find(i)] = find(j);
			}
		if (vis) key[i] = ++m;
		else key[i] = -1;
	}
	for (int i = 1; i <= n; i++)
		f[i] = find(i);
	assert(m <= 15);
	int ans = 1, cnt = 0;
	for (int i = 1; i <= n; i++)
		if (f[i] == i) {
			ans = 1ll * ans * getans(i) % P * binom[cnt + getcnt(i)][cnt] % P;
			cnt += getcnt(i);
		}
	cout << ans << endl;
	return 0;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/104052408