【AtCoder】AtCoder Grand Contest 041

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Table Tennis Training

A , B A,B 奇偶性相同,则答案为 B A 2 \frac{B-A}{2}

否则,答案为 M i n { A + B 1 , 2 N A B + 1 } 2 \frac{Min\{A+B-1,2N-A-B+1\}}{2}

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

#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 main() {
	ll n, a, b; read(n), read(a), read(b);
	if ((b - a) % 2 == 0) cout << (b - a) / 2 << endl;
	else cout << min(a + b - 1, 2 * n - a - b + 1) / 2 << endl;
	return 0;
}

Problem B. Voting Judges

二分答案,考虑如何判断某个题是否能够能够出线。

问题可以规约为如下新问题:
给定数组 a i a_i ,两个整数 x , y x,y
可以进行 x x 次操作,每次选择 y y 个数 1 -1 ,问是否能够使得 a i 0 a_i\leq 0

考虑每次操作最大的 y y a i a_i ,可以发现可以满足条件当且仅当 a i x , a i x × y a_i\leq x,\sum{a_i}\leq x\times y

时间复杂度 O ( N L o g N ) O(NLogN)

#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 n, m, v, p, a[MAXN];
bool check(int pos) {
	int kill = n - p; ll sum = 0;
	for (int i = n; i >= 1 && kill; i--)
		if (i != pos) {
			if (a[pos] - a[i] > m) return false;
			sum += max(a[pos] - a[i], 0), kill--;
		}
	return sum <= 1ll * m * v;
}
int main() {
	read(n), read(m), read(v), read(p), v = n - v;
	for (int i = 1; i <= n; i++)
		read(a[i]), a[i] = 1e9 - a[i];
	sort(a + 1, a + n + 1);
	int l = 1, r = n;
	while (l < r) {
		int mid = (l + r + 1) / 2;
		if (check(mid)) l = mid;
		else r = mid - 1;
	}
	cout << l << endl;
	return 0;
}

Problem C. Domino Quality

N = 3 N=3 时可以简单构造,且可以通过重复这个构造解决 N N 3 3 的倍数的情况。

N N 4 \geq 4 的偶数时,可以简单构造,使得各行各列的权值均为 3 3

构造 N = 5 , 7 N=5,7 时的解,对于其余是奇数的 N N ,可以拼一个 N = 5 N=5 的构造使得 N N 变为偶数。

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

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e3 + 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][MAXN];
int main() {
	int n; read(n);
	if (n == 2) {
		puts("-1");
		return 0;
	}
	if (n == 5) {
		puts("aabbc");
		puts("bb.ec");
		puts("..fed");
		puts("c.f.d");
		puts("cbbaa");
		return 0;
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		s[i][j] = '.';
	if (n % 3 == 0) {
		for (int i = 1, j = 1; i <= n / 3; i++, j += 3) {
			s[j + 0][j + 0] = s[j + 0][j + 1] = 'a';
			s[j + 1][j + 2] = s[j + 2][j + 2] = 'a';
		}
	} else {
		int tn = n;
		if (n & 1) {
			int m = n - 7; tn -= 7;
			s[m + 1][m + 1] = s[m + 1][m + 2] = 'a';
			s[m + 1][m + 3] = s[m + 1][m + 4] = 'b';
			s[m + 1][m + 5] = s[m + 1][m + 6] = 'c';
			s[m + 2][m + 1] = s[m + 2][m + 2] = 'd';
			s[m + 2][m + 3] = s[m + 2][m + 4] = 'e';
			s[m + 3][m + 1] = s[m + 3][m + 2] = 'f';
			s[m + 3][m + 3] = s[m + 3][m + 4] = 'g';
			
			s[m + 2][m + 7] = s[m + 3][m + 7] = 'a';
			s[m + 4][m + 7] = s[m + 5][m + 7] = 'b';
			s[m + 6][m + 7] = s[m + 7][m + 7] = 'c';
			s[m + 4][m + 6] = s[m + 5][m + 6] = 'd';
			s[m + 6][m + 6] = s[m + 7][m + 6] = 'e';
			s[m + 4][m + 5] = s[m + 5][m + 5] = 'f';
			s[m + 6][m + 5] = s[m + 7][m + 5] = 'g';
		}
		for (int i = 1, j = 1; i <= tn / 2; i++, j += 2) {
			s[j + 0][j + 0] = s[j + 0][j + 1] = 'a';
			s[j + 1][j + 0] = s[j + 1][j + 1] = 'b';
			if (i != tn / 2) {
				s[j + 2][j + 0] = s[j + 3][j + 0] = 'c';
				s[j + 2][j + 1] = s[j + 3][j + 1] = 'd';
			} else {
				s[1][j + 0] = s[2][j + 0] = 'c';
				s[1][j + 1] = s[2][j + 1] = 'd';
			}
		}
	}
	for (int i = 1; i <= n; i++)
		printf("%s\n", s[i] + 1);
	return 0;
}

Problem D. Problem Scores

题目中给出的第 i i 个限制为
j = 1 i + 1 A j > j = N i + 1 N A j \sum_{j=1}^{i+1}A_j>\sum_{j=N-i+1}^{N}A_j

注意到对于第 i   ( i > N 1 2 ) i\ (i>\lfloor\frac{N-1}{2}\rfloor) 个限制,在两边减去重复的元素后可以变为一个已经存在的限制,因此我们只需要考虑前 M = N 1 2 M=\lfloor\frac{N-1}{2}\rfloor 个限制。

d p i , j , k dp_{i,j,k} 表示已经考虑了前 i i 个限制,目前 x = 1 i + 1 A x x = N i + 1 N A x = j \sum_{x=1}^{i+1}A_x-\sum_{x=N-i+1}^{N}A_x=j ,且 A N i + 1 A i + 1 = k A_{N-i+1}-A_{i+1}=k 时可行的方案数,转移时枚举 A N i A i + 2 A_{N-i}-A_{i+2} 即可。

由此,可以得到一个时间复杂度 O ( N 4 ) O(N^4) 的 DP 做法。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
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, P;
int dp[MAXN][MAXN][MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), read(P);
	for (int i = 1; i <= n; i++)
		dp[0][i][n - i] = 1;
	for (int t = 1; t <= (m = (n - 1) / 2); t++)
	for (int i = 1; i <= n; i++)
	for (int j = 0; j <= n - 1; j++) {
		int tmp = dp[t - 1][i][j];
		if (dp[t - 1][i][j] == 0) continue;
		for (int k = 0; k <= j && k <= i - 1; k++)
			update(dp[t][i - k][k], 1ll * tmp * (j - k + 1) % P);
	}
	int ans = 0;
	if (n & 1) {
		for (int i = 1; i <= n; i++)
		for (int j = 0; j <= n - 1; j++)
			update(ans, dp[m][i][j]);
	} else {
		for (int i = 1; i <= n; i++)
		for (int j = 0; j <= n - 1; j++)
			update(ans, 1ll * dp[m][i][j] * (j + 1) % P);
	}
	cout << ans << endl;
	return 0;
}

上述做法的复杂度瓶颈在于 DP 。

x 0 = N A 1 , x i = A N i + 1 A i + 1   ( i 1 ) x_0=N-A_1,x_i=A_{N-i+1}-A_{i+1}\ (i\geq 1)

成为复杂度瓶颈的 DP 中,状态中的 k k 即为 x i x_i ,这个 DP 计算了满足
x i x i 1   ( i 1 ) , i = 1 M x i A 1 x_i\leq x_{i-1}\ (i\geq 1),\sum_{i=1}^{M}x_i\leq A_1 的所有 { x i } \{x_i\} 对应的 i = 1 M ( x i 1 x i + 1 ) \prod_{i=1}^{M}(x_{i-1}-x_i+1) 的和。

注意到限制 i = 1 M x i A 1 \sum_{i=1}^{M}x_i\leq A_1 不是很好处理,可以考虑枚举 e = N i = 0 M x i 0 e=N-\sum_{i=0}^{M}x_i\geq 0 ,则这个限制可以写为 i = 0 M x i + e = N \sum_{i=0}^{M}x_i+e=N

由此,可以将 DP 的状态改为两维,即 x i x_i 的个数,以及 x i x_i 的总和,由于 x i x_i 是有序的,转移时只需要枚举最小的 x i x_i 是多少即可,转移的复杂度应当用调和级数分析。

时间复杂度 O ( N 2 L o g N ) O(N^2LogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
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, P;
int dp[MAXN][MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), read(P), m = (n - 1) / 2;
	dp[0][0] = 1;
	for (int i = 1; i <= m; i++)
	for (int j = 0; j <= n; j++) {
		int tmp = 0;
		for (int k = 0; k * i <= j; k++)
			update(tmp, 1ll * dp[i - 1][j - k * i] * (k + 1) % P);
		dp[i][j] = tmp;
	}
	int ans = 0;
	if (n & 1) {
		for (int i = 1; i <= n; i++)
		for (int j = 0; j * (m + 1) <= n - i; j++)
			update(ans, dp[m][n - i - j * (m + 1)]);
	} else {
		for (int i = 1; i <= n; i++)
		for (int j = 0; j * (m + 1) <= n - i; j++)
			update(ans, 1ll * dp[m][n - i - j * (m + 1)] * (j + 1) % P);
	}
	cout << ans << endl;
	return 0;
}

上面的 DP 是可以进一步优化的,转移系数 x i 1 x i + 1 x_{i-1}-x_i+1 可以看做在 x i x_i x i 1 x_{i-1} 中选择一个关键点的方案数,则在 DP 中增设一维 0 / 1 0/1 ,表示是否选取过关键点即可。

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

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
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, P, dp[MAXN][MAXN][2];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), read(P), m = (n - 1) / 2;
	dp[0][0][0] = 1;
	for (int i = 1; i <= m; i++)
	for (int j = 0; j <= n; j++) {
		update(dp[i][j][1], dp[i - 1][j][0]);
		if (j >= i) update(dp[i][j][1], dp[i][j - i][1]);
		dp[i][j][0] = dp[i][j][1];
		if (j >= i) update(dp[i][j][0], dp[i][j - i][0]);
	}
	int ans = 0;
	if (n & 1) {
		for (int i = 1; i <= n; i++)
		for (int j = 0; j * (m + 1) <= n - i; j++)
			update(ans, dp[m][n - i - j * (m + 1)][0]);
	} else {
		for (int i = 1; i <= n; i++)
		for (int j = 0; j * (m + 1) <= n - i; j++)
			update(ans, 1ll * dp[m][n - i - j * (m + 1)][0] * (j + 1) % P);
	}
	cout << ans << endl;
	return 0;
}

Problem E. Balancing Network

对于 T = 1 T=1 的问题,考虑判断是否能够将所有线路接到某条线路 i i

则应当将操作序列反过来,依次考虑每个操作,若一个操作连接了一个可以到达 i i 的线路和一个不能到达 i i 的线路,则可以通过操作使得它们都可以到达 i i

由此,我们有了一个 O ( N × M ) O(N\times M) 的做法,用 b i t s e t bitset 优化它即可。

对于 T = 2 T=2 的问题,依然考虑则应当将操作序列反过来。

r e s i res_i 表示线路 i i 最终到达的线路,则初始时显然有 r e s i = i res_i=i

对于操作 x y x\rightarrow y ,在 r e s res 数组中的体现即为 r e s x r e s y res_x\leftarrow res_y

我们希望最终 r e s res 数组中至少有两种数,修改出现次数较多的一个数即可。

时间复杂度 O ( N × M w ) O(\frac{N\times M}{w}) ,其中 w = 64 w=64

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
const int MAXM = 1e5 + 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[MAXM]; bitset <MAXN> a[MAXN];
int n, m, t, x[MAXM], y[MAXM], b[MAXN], c[MAXN];
int main() {
	read(n), read(m), read(t);
	if (t == 1) {
		for (int i = 1; i <= n; i++)
			a[i].set(i);
		for (int i = 1; i <= m; i++) {
			read(x[i]), read(y[i]);
			a[x[i]] = a[y[i]] = a[x[i]] | a[y[i]];
		}
		for (int i = 1; i <= n; i++)
			if (a[i].count() == n) {
				static bool vis[MAXN];
				vis[i] = true;
				for (int i = m; i >= 1; i--) {
					if (vis[x[i]]) {
						s[i] = '^';
						vis[y[i]] = true;
					} else if (vis[y[i]]) {
						s[i] = 'v';
						vis[x[i]] = true;
					} else s[i] = '^';
				}
				printf("%s\n", s + 1);
				return 0;
			}
		puts("-1");
	} else {
		if (n == 2) puts("-1");
		else {
			for (int i = 1; i <= n; i++)
				b[i] = i, c[i] = 1;
			for (int i = 1; i <= m; i++)
				read(x[i]), read(y[i]);
			for (int i = m; i >= 1; i--) {
				if (b[x[i]] == b[y[i]]) s[i] = '^';
				else if (c[b[x[i]]] >= 2) {
					c[b[x[i]]]--;
					b[x[i]] = b[y[i]];
					c[b[x[i]]]++;
					s[i] = 'v';
				} else {
					c[b[y[i]]]--;
					b[y[i]] = b[x[i]];
					c[b[y[i]]]++;
					s[i] = '^';
				}
			}
			printf("%s\n", s + 1);
		}
	}
	return 0;
}

Problem F. Histogram Rooks

考虑给定的图通过移除最下方一行,以及断开已经没有元素的列形成的子图。

对于一个子图中的列,有如下三种可能:
( 1 ) (1) 、已经存在一个车
( 2 ) (2) 、不存在车,但各行均被某个车的攻击范围覆盖
( 3 ) (3) 、不存在车,且存在没有被某个车的攻击范围覆盖的格子

关键的一点是注意到若某一行下方没有不存在车的行,则 ( 2 ) (2) 类列可以被视为 ( 1 ) (1) 类列;否则 ( 2 ) (2) 类列可以被视为 ( 3 ) (3) 类列。

那么,将某一行下方没有不存在车的行决定好,作为一个 0 / 1 0/1 位计入状态,再在状态中计入 ( 3 ) (3) 类列的个数就可以转移了。转移即考虑移除当前图最下方的一行,或是断开已经没有元素的列。

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

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 405;
const int MAXM = 805;
const int P = 998244353;
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, tot, num[MAXN][MAXN], ptwo[MAXN], a[MAXN];
int dp[MAXM][MAXN][MAXN][2], size[MAXM], binom[MAXN][MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int work(int l, int r, int h) {
	if (l > r) {
		dp[0][h][0][0] = 1;
		dp[0][h][0][1] = 1;
		return 0;
	}
	int &res = num[l][r];
	if (res == 0) res = ++tot;
	int Min = n, pos = l;
	for (int i = l; i <= r; i++)
		if (a[i] < Min) {
			Min = a[i];
			pos = i;
		}
	if (Min == h) {
		int x = work(l, pos - 1, h);
		int y = work(pos + 1, r, h);
		size[res] = size[x] + size[y] + 1;
		for (int i = 0; i <= size[x]; i++)
		for (int j = 0; j <= size[y]; j++) {
			update(dp[res][h][i + j + 0][0], 1ll * dp[x][h][i][0] * dp[y][h][j][0] % P);
			update(dp[res][h][i + j + 1][1], 1ll * dp[x][h][i][1] * dp[y][h][j][1] % P);
		}
	} else {
		work(l, r, h + 1);
		for (int i = 0; i <= size[res]; i++) {
			update(dp[res][h][i][0], dp[res][h + 1][i][1]);
			update(dp[res][h][i][1], dp[res][h + 1][i][1]);
			
			update(dp[res][h][i][0], 1ll * dp[res][h + 1][i][0] * (ptwo[size[res] - i] - 1) % P);
			update(dp[res][h][i][1], 1ll * dp[res][h + 1][i][1] * (ptwo[size[res] - i] - 1) % P);
		}
		for (int i = 1; i <= size[res]; i++)
		for (int j = 1; j <= i; j++) {
			update(dp[res][h][i - j][0], 1ll * dp[res][h + 1][i][0] * ptwo[size[res] - i] % P * binom[i][j] % P);
			update(dp[res][h][i - j][1], 1ll * dp[res][h + 1][i][1] * ptwo[size[res] - i] % P * binom[i][j] % P);
		}
	}
	return res;
}
int main() {
	read(n), ptwo[0] = binom[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		read(a[i]), binom[i][0] = 1;
		ptwo[i] = 2 * ptwo[i - 1] % P;
		for (int j = 1; j <= i; j++)
			binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % P;
	}
	int res = work(1, n, 0);
	cout << dp[res][0][0][0] << endl;
	return 0;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

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