【AtCoder】AtCoder Grand Contest 039

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Connection and Disconnection

令最终字符串中每一段连续的字符长度为 X i X_i ,答案显然是 X i 2 \sum \lfloor\frac{X_i}{2}\rfloor

进行简单分类讨论即可。

时间复杂度 O ( S ) O(|S|)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int m, len[MAXN];
char s[MAXN], t[MAXN];
int main() {
	scanf("%s", s + 1);
	int n; n = strlen(s + 1);
	ll ans = 0, k; read(k);
	for (int i = 1; i <= n; i++)
		if (s[i] == t[m]) len[m]++;
		else t[++m] = s[i], len[m] = 1;
	if (m == 1) ans = n * k / 2;
	else if (t[1] == t[m]) {
		ans += len[1] / 2;
		ans += len[m] / 2;
		ans += (len[1] + len[m]) / 2 * (k - 1);
		for (int i = 2; i <= m - 1; i++)
			ans += len[i] / 2 * k;
	} else {
		for (int i = 1; i <= m; i++)
			ans += len[i] / 2 * k;
	}
	writeln(ans);
	return 0;
}

Problem B. Graph Partition

首先考虑无解的问题,不难发现当且仅当图是二分图,问题有解。

判断有解后,考虑枚举 V 1 V_1 ,并从 V 1 V_1 的所有点出发进行 BFS ,各个点对应的距离 + 1 +1 即为它们被分入的组。

并且,可以发现我们可以假设 V 1 V_1 中只有一个点,而答案不会变劣,这是因为我们完全可以将 V 1 V_1 中其余的点划分入 V 3 V_3 。因此,我们只需要枚举 O ( N ) O(N) V 1 V_1 即可。

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

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
char s[MAXN][MAXN];
int n, dist[MAXN];
bool vis[MAXN], col[MAXN];
void bfs(int pos) {
	static int q[MAXN];
	int l = 0, r = 0; q[0] = pos;
	memset(dist, 0, sizeof(dist));
	dist[pos] = 1;
	while (l <= r) {
		int pos = q[l++];
		for (int i = 1; i <= n; i++)
			if (s[pos][i] == '1' && dist[i] == 0) {
				dist[i] = dist[pos] + 1;
				q[++r] = i;
			}
	}
}
void dfs(int pos) {
	vis[pos] = true;
	for (int i = 1; i <= n; i++)
		if (s[pos][i] == '1') {
			if (vis[i]) {
				if (col[i] == col[pos]) {
					puts("-1");
					exit(0);
				}
			} else {
				col[i] = !col[pos];
				dfs(i);
			}
		}
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		scanf("\n%s", s[i] + 1);
	dfs(1);
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		bfs(i);
		for (int j = 1; j <= n; j++)
			chkmax(ans, dist[j]);
	}
	writeln(ans);
	return 0;
}

Problem C. Division by Two with Something

在二进制表示下考虑题目中的操作,即删去数字的最低位,并将相反的数接在最高位之前。

那么,不难发现在 2 N 2N 次操作后,数字就会恢复原状。

进一步地,在 2 i 2i 次操作后,数字会恢复原状,当且仅当 i i N N 的因数, N i \frac{N}{i} 是奇数,并且若将数字每 i i 位分为一组,相邻的组对应位置的数字恰好相反。

则可以用容斥原理计算答案,从大到小枚举 i i ,计算此类数字已经被计算的贡献,用目标贡献减之,作为新贡献计入答案。

时间复杂度 O ( N d ( N ) + N L o g N ) O(Nd(N)+NLogN) ,其中 d ( N ) d(N) 表示 N N 的因数个数。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
char s[MAXN], t[MAXN];
bool vis[MAXN];
int n, ans, val[MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int cnt(int x) {
	int ans = 0;
	for (int i = 1; i <= x; i++)
		ans = ((ans * 2) + (s[i] - '0')) % P;
	for (int i = 1, j = 1; i <= n; i++, j = (j == 2 * x) ? 1 : (j + 1))
		if (j <= x) t[i] = s[j];
		else t[i] = s[j - x] ^ 1;
	for (int i = 1; i <= n; i++) {
		if (s[i] > t[i]) return ans + 1;
		if (t[i] > s[i]) return ans;
	}
	return ans + 1;
}
int main() {
	read(n), scanf("%s", s + 1);
	for (int i = 1; i <= n; i++)
		if (n % i == 0 && (n / i) % 2 == 1) vis[i] = true;
	int ans = 1ll * cnt(n) * (2 * n) % P;
	for (int i = n - 1; i >= 1; i--) {
		if (!vis[i]) continue;
		int now = 2 * n;
		for (int j = i; j <= n; j += i)
			if (vis[j]) update(now, val[j]);
		val[i] = 2 * i;
		update(val[i], P - now);
		update(ans, 1ll * cnt(i) * val[i] % P);
	}
	writeln(ans);
	return 0;
}

Problem D. Incenters

"I prefer solving problems about world history rather than MO geometry."

对于选定的三个点 A , B , C A,B,C ,令 A A' 为不经过 A A 的圆弧 B C BC 的中点, B , C B',C' 同理。

可以证明, A B C \triangle ABC 的内心与 A B C \triangle A'B'C' 的重心重合。

而在 A B C \triangle A'B'C' 中,考虑三角形的 欧拉线 ,令外心为 O O ,中心为 G G ,重心为 H H ,则 H H O G OG 的延长线上,并且 H G = 2 O G |HG|=2|OG| 。原题中,显然 O = ( 0 , 0 ) O=(0,0) ,因此若能计算出 G G 坐标的期望,便能计算出 H H 坐标的期望

显然 G = A + B + C 3 G=\frac{A'+B'+C'}{3} ,因此只需要枚举两个点计算 A A' 的贡献即可。

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

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const double pi = acos(-1);
const double eps = 1e-10;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
double a[MAXN];
int main() {
	int n, l; read(n), read(l);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	double ansx = 0, ansy = 0;
	for (int i = 1; i <= n; i++)
	for (int j = i + 1; j <= n; j++) {
		ansx += cos(pi * (a[i] + a[j]) / l) * (n - (j - i + 1));
		ansy += sin(pi * (a[i] + a[j]) / l) * (n - (j - i + 1));
		ansx += cos(pi * (a[i] + a[j] + l) / l) * (j - i - 1);
		ansy += sin(pi * (a[i] + a[j] + l) / l) * (j - i - 1);
	}
	double cnt = 1.0 * n * (n - 1) * (n - 2) / 6;
	printf("%.10lf %.10lf\n", ansx / cnt, ansy / cnt);
	return 0;
}

Problem E. Pairing Points

首先考虑与 1 1 配对的点,不妨令为点 i i

那么,我们需要对 [ 2 , i ) ( i , N ] [2,i)\cup(i,N] 继续配对,并且,横跨两个区间的边至少要有一条,若有多条,则需要满足它们的端点之间存在单调性,即保证连线不成环。

考虑枚举最靠外侧的横跨两个区间的边 ( j , k ) (j,k)

( j , i ) (j,i) 之间的点或是与 j j 连通,或是与 i i 连通,并且存在一个分界线,考虑枚举它,记为 p p ,同理,枚举与 i i k k 连通的分界线 q q

由于 ( j , k ) (j,k) 是最靠外侧的横跨两个区间的边,因此,对于区间 [ 2 , p ] [2,p] 内的点,我们不再需要知晓区间 [ i , N ] [i,N] 内的信息,从而递归为了一个点集 [ 2 , j ) ( j , p ] [2,j)\cup(j,p] 的子问题。同理, 区间 [ q , N ] [q,N] 内的点递归为了一个点集 [ q , k ) ( k , N ] [q,k)\cup(k,N] 的子问题,而剩余的点则递归为了一个点集 [ p + 1 , i ) ( i , q 1 ] [p+1,i)\cup(i,q-1] 的子问题。

由此进行动态规划,时间复杂度 O ( N 7 ) O(N^7)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 55;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
char s[MAXN][MAXN];
ll dp[MAXN][MAXN][MAXN];
ll getans(int l, int r, int m) {
	if (dp[l][r][m] != -1) return dp[l][r][m];
	if (l == r) return dp[l][r][m] = 1;
	if (l == m || r == m) return dp[l][r][m] = 0;
	ll &res = dp[l][r][m]; res = 0;
	for (int i = l; i <= m - 1; i++)
	for (int j = m + 1; j <= r; j++) {
		if (s[i][j] == '0') continue;
		for (int s = i; s <= m - 1; s++)
		for (int t = m + 1; t <= j; t++)
			res += getans(l, s, i) * getans(s + 1, t - 1, m) * getans(t, r, j);
	}
	return res;
}
int main() {
	int n; read(n);
	for (int i = 1; i <= n * 2; i++)
		scanf("\n%s", s[i] + 1);
	memset(dp, -1, sizeof(dp));
	ll ans = 0;
	for (int i = 2; i <= n * 2; i++)
		if (s[1][i] == '1') ans += getans(2, n * 2, i);
	writeln(ans);
	return 0;
}

Problem F. Min Product Sum

考虑一个矩阵权值的组合意义,可以认为除了矩阵 A A 外,我们还填写了矩阵 B B ,并且,矩阵 B B 各行各列的最大值不超过矩阵 A A 对应行列的最小值,求总方案数。

令矩阵 B B 各行的最大值为 X i X_i ,矩阵 A A 各列的最小值为 Y i Y_i

我们希望得到一个动态规划解法,而状态实际上已经确定,应当为已经确定的行数 i i 、列数 j j 、数字数 k k ,即状态数是 O ( N M K ) O(NMK) 级别的。因此,我们需要一个线性的转移,但是,若状态 i , j i,j 都是用来描述矩阵 A A 的,在转移时,我们就不得不枚举两维进行转移,从而超过了需要的线性复杂度。

由此,可以想到用其中一个状态描述 A A ,另一个状态描述 B B

d p i , j , k dp_{i,j,k} 表示已经考虑了 X i k X_i\leq k i i 行和 Y i k Y_i\leq k j j 列,此时已经确定的数的填法总数。

转移时首先考虑枚举 X i = k + 1 X_i=k+1 的行数,对于每一个这样的行,可以在已经确定 Y i k Y_i\leq k j j 列填上 k + 1 \geq k+1 A A 中的元素,并在尚未确定 Y i k Y_i\leq k M j M-j 列填上 k + 1 \leq k+1 B B 中的元素,并保证至少有一个填入的数 = k + 1 =k+1

再考虑枚举 Y i = k + 1 Y_i=k+1 的列数,对于每一个这样的列,可以在已经确定 X i k X_i\leq k i i 行填上 k + 1 \geq k+1 A A 中的元素,并保证至少有一个填入的数 = k + 1 =k+1 ,并在尚未确定 X i k X_i\leq k N i N-i 行填上 k \leq k B B 中的元素。

不难发现上述两个转移可以认为是转移的两个阶段,因此,只需要给状态加一维 0 / 1 0/1 即可。

预处理转移系数,时间复杂度 O ( N M K ( N + M ) ) O(NMK(N+M))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, k, P, dp[MAXN][MAXN][MAXN][2];
int binom[MAXN][MAXN], coef[MAXN][MAXN][MAXN][2];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int main() {
	read(n), read(m), read(k), read(P), dp[1][0][0][0] = 1;
	for (int i = 0; i <= max(n, m); 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;
	}
	for (int t = 1; t <= k; t++)
	for (int i = 0; i <= m; i++) {
		for (int j = 0; j <= n; j++) {
			int tmp = (power(t, m - i) - power(t - 1, m - i) + P) % P;
			tmp = 1ll * tmp * power(k - t + 1, i) % P;
			coef[t][i][j][0] = power(tmp, j);
		}
	}
	for (int t = 1; t <= k; t++)
	for (int i = 0; i <= n; i++) {
		for (int j = 0; j <= m; j++) {
			int tmp = (power(k - t + 1, i) - power(k - t, i) + P) % P;
			tmp = 1ll * tmp * power(t, n - i) % P;
			coef[t][i][j][1] = power(tmp, j);
		}
	}
	for (int t = 1; t <= k; t++) {
		for (int i = 0; i <= n; i++)
		for (int j = 0; j <= m; j++) {
			int tmp = dp[t][i][j][0];
			for (int k = 0; i + k <= n; k++)
				update(dp[t][i + k][j][1], 1ll * tmp * binom[n - i][k] % P * coef[t][j][k][0] % P);
			tmp = dp[t][i][j][1];
			for (int k = 0; j + k <= m; k++)
				update(dp[t + 1][i][j + k][0], 1ll * tmp * binom[m - j][k] % P * coef[t][i][k][1] % P);
		}
	}
	writeln(dp[k + 1][n][m][0]);
	return 0;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

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