【AtCoder】AtCoder Grand Contest 046

比賽鏈接

點擊打開鏈接

官方題解

點擊打開鏈接

Problem A. Takahashikun, The Strider

可以發現,任意時刻,玩家均位於以前兩次操作路徑的中垂線的交點上。
因此,答案即爲使得玩家朝向與初始時第一次一致的時刻,即
360gcd(X,360)\frac{360}{gcd(X,360)}

時間複雜度 O(LogV)O(LogV)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 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() {
	int n; read(n);
	cout << 360 / __gcd(n, 360) << endl;
	return 0;
}

Problem B. Extension

dpi,j (iA,jB)dp_{i,j}\ (i\geq A, j\geq B) ,表示 C=i,D=jC=i,D=j 時,問題的答案。
則顯然有
dpA,j=AjBdp_{A,j}=A^{j-B}

對於 dpi,j (i>A,jB)dp_{i,j}\ (i>A, j\geq B) ,考慮枚舉第一行第二個黑色的格子的位置,應當有
dpi,j=dpi1,j×j+k=1j1ik1×(jk)×dpi1,jkdp_{i,j}=dp_{i-1,j}\times j+\sum_{k=1}^{j-1}i^{k-1}\times (j-k)\times dp_{i-1,j-k}

用部分和優化轉移,可以做到均攤 O(1)O(1)
時間複雜度 O(C×D)O(C\times D)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e3 + 5;
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 dp[MAXN][MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	int a, b, c, d; read(a), read(b), read(c), read(d);
	dp[a][b] = 1;
	for (int j = b + 1; j <= d; j++)
		dp[a][j] = 1ll * dp[a][j - 1] * a % P;
	for (int i = a + 1; i <= c; i++) {
		int sum = 0;
		for (int j = b; j <= d; j++) {
			update(dp[i][j], 1ll * dp[i - 1][j] * j % P);
			update(dp[i][j], sum);
			sum = 1ll * sum * i % P;
			update(sum, 1ll * dp[i - 1][j] * j % P);
		}
	}
	cout << dp[c][d] << endl;
	return 0;
}

Problem C. Shift

將字符串中的 00 看做分隔符,記序列 aia_i 表示相鄰的兩個 00 之間 11 的個數。
則一次操作相當於選擇 (i,j) (i<j)(i,j)\ (i<j) ,令 ai=ai+1,aj=aj1a_i=a_i+1,a_j=a_j-1

由於合法的序列 aia_i 與合法的字符串一一對應,考慮對序列 aia_i 計數。
則記 dpi,j,kdp_{i,j,k} 表示決定了 a1,a2,,aia_1,a_2,\dots,a_i ,其相較於原字符串總的增加量爲 jj ,進行了 kk 次操作的方案數,顯然有 O(S)O(|S|) 轉移,則可以得到一個 O(S4)O(|S|^4) 的做法。

用部分和優化轉移,時間複雜度 O(S3)O(|S|^3)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
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;
}
char s[MAXN];
int n, l, m, a[MAXN];
int dp[MAXN][MAXN][MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	scanf("\n%s%d", s + 1, &m);
	int last = 0; l = strlen(s + 1), chkmin(m, l);
	for (int i = 1; i <= l + 1; i++)
		if (s[i] != '1') {
			a[++n] = i - last - 1;
			last = i;
		}
	dp[0][0][0] = 1;
	for (int i = 1; i <= n; i++) {
		static int b[MAXN][MAXN], c[MAXN][MAXN];
		memset(b, 0, sizeof(b));
		memset(c, 0, sizeof(c));
		for (int j = 0; j <= l; j++)
		for (int k = 0; k <= m; k++) {
			int tmp = dp[i - 1][j][k];
			if (tmp == 0) continue;
			update(b[j - min(a[i], j)][k], tmp);
			update(b[j + 1][k], P - tmp);
			update(c[j + 1][k + 1], tmp);
		}
		for (int j = 0; j <= l; j++)
		for (int k = 0; k <= m; k++) {
			if (j != 0) update(b[j][k], b[j - 1][k]);
			update(dp[i][j][k], b[j][k]);
		}
		for (int j = 0; j <= l; j++)
		for (int k = 0; k <= m; k++) {
			if (j != 0 && k != 0) update(c[j][k], c[j - 1][k - 1]);
			update(dp[i][j][k], c[j][k]);
		}
	}
	int ans = 0;
	for (int i = 0; i <= m; i++)
		update(ans, dp[n][0][i]);
	cout << ans << endl;
	return 0;
}

Problem D. Secret Passage

考慮如何判斷一個給定的字符串是否可以被生成。

令所判斷的字符串爲 TT ,考慮 S,TS,T 的最後一個字符:
若最後一個字符相同,則 SS 能夠生成 TT 當且僅當刪去一個字符的 SS 可以生成刪去一個字符的 TT
若最後一個字符不同,則必須要通過給定的操作在此處插入一個字符。

對此判定過程設計狀態 (i,j,k)(i,j,k) ,表示當前 T=i|T|=i ,需要通過操作插入 jj00kk11
設計動態規劃對各個狀態的個數進行計數,顯然有 O(1)O(1) 轉移。

考慮如何判斷狀態 (i,j,k)(i,j,k) 是否對應着可以生成的字符串 TT 。則我們要求在僅對字符串的前 S(ijk)|S|-(i-j-k) 位進行操作的情況下,可以向後插入 jj00kk11

對於 SS 中的前 22 個字符,我們可以選擇進行一次操作,將其中一個字符插入到後面,也可以選則將任意一個字符插入到前 S(ijk)|S|-(i-j-k) 個字符中,有了這樣的字符,我們便可以選擇操作 SS 中的第 11 個字符和一個被事先插入的字符了。

因此,記 dpi,j,kdp_{i,j,k} 表示對 SS 的前 ii 個字符進行操作,可以向後插入 jj00kk11 的情況下,還可以向前 S(ijk)|S|-(i-j-k) 個字符中至多可以插入的字符數,同樣有 O(1)O(1) 轉移。

時間複雜度 O(S3)O(|S|^3)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
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;
}
char s[MAXN]; int n;
int dp[MAXN][MAXN][MAXN];
int vis[MAXN][MAXN][MAXN];
bool res[MAXN][MAXN][MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
bool check(int x, int y, int z) {
	return res[x][y][z];
}
int main() {
	scanf("%s", s + 1), n = strlen(s + 1), dp[n][0][0] = 1;
	for (int i = n; i >= 1; i--)
	for (int j = 0; j + (n - i) <= n - 1; j++)
	for (int k = 0; j + k + (n - i) <= n - 1; k++)
		if (s[i] == '0') {
			update(dp[i - 1][j][k], dp[i][j][k]);
			update(dp[i][j][k + 1], dp[i][j][k]);
		} else {
			update(dp[i - 1][j][k], dp[i][j][k]);
			update(dp[i][j + 1][k], dp[i][j][k]);
		}
	memset(vis, -1, sizeof(vis));
	vis[0][0][0] = 0;
	for (int i = 0; i <= n; i++)
	for (int j = 0; j <= i / 2; j++)
	for (int k = 0; j + k <= i / 2; k++) {
		if (vis[i][j][k] == -1) continue;
		int tmp = vis[i][j][k];
		if (i + 1 <= n && tmp != 0) {
			if (s[i + 1] == '0') chkmax(vis[i + 1][j + 1][k], tmp - 1);
			if (s[i + 1] == '1') chkmax(vis[i + 1][j][k + 1], tmp - 1);
		}
		if (i + 2 <= n) {
			if (s[i + 1] == '0' || s[i + 2] == '0') chkmax(vis[i + 2][j + 1][k], tmp);
			if (s[i + 1] == '1' || s[i + 2] == '1') chkmax(vis[i + 2][j][k + 1], tmp);
			chkmax(vis[i + 2][j][k], tmp + 1);
		}
	}
	for (int i = 0; i <= n; i++)
	for (int j = n; j >= 0; j--)
	for (int k = n; k >= 0; k--) {
		res[i][j][k] = vis[i][j][k] != -1;
		if (i != 0) res[i][j][k] |= res[i - 1][j][k];
		res[i][j][k] |= res[i][j + 1][k];
		res[i][j][k] |= res[i][j][k + 1];
	}
	int ans = 0;
	for (int i = 0; i <= n; i++)
	for (int j = 0; j + (n - i) <= n; j++)
	for (int k = 0; j + k + (n - i) <= n; k++)
		if (dp[i][j][k] && (n - i) + j + k != 0) {
			if (check(i, j, k)) {
				update(ans, dp[i][j][k]);
			}
		}
	cout << ans << endl;
	return 0;
}

Problem E. Permutation Cover

考慮如何判斷答案是否爲 1-1
則可以發現,答案爲 1-1 當且僅當
2×min{ai}+1max{ai}2\times \min\{a_i\}+1\geq \max\{a_i\}

對不不滿足以上條件的情況,考慮出現次數最少的元素,不難構造一種方案。

由於我們需要確定字典序最小的解,我們還需要能夠判定以給定排列 {pi}\{p_i\} 開頭的的方案是否存在。那麼,令 aia_i 表示剩餘各個元素的出現次數,若
2×min{ai}max{ai}2\times \min\{a_i\}\leq \max\{a_i\}
則顯然是存在方案的,而相比於剛開始的判定問題,不同的是,若
2×min{ai}+1=max{ai}2\times \min\{a_i\}+1=\max\{a_i\}
也是有可能存在方案的。

進一步地,此時,存在方案當且僅當在 {pi}\{p_i\} 中,使得 aia_i 取到最大的 ii 均出現在使得 aia_i 取到最小的 ii 的前面。由此,枚舉接在 pip_i 後的新排列的長度,我們可以貪心地找到能夠拼接上去的,字典序最小的序列。

時間複雜度 O((ai)×K2LogK)O((\sum a_i)\times K^2LogK)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
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, s, a[MAXN];
int x, y, cur[MAXN], res[MAXN], ans[MAXN];
bool cnp(int s, int t) {
	if ((a[s] == x) == (a[t] == x)) return s < t;
	else return (a[s] == x) < (a[t] == x);
}
void work(int len) {
	bool valid = true, found = false;
	for (int i = 1; i <= len; i++)
		a[cur[i]]--;
	int Min = a[1], Max = a[1];
	for (int i = 2; i <= n; i++) {
		chkmin(Min, a[i]);
		chkmax(Max, a[i]);
	}
	if (Min * 2 >= Max) {
		found = true;
		for (int i = 1; i <= len; i++)
			res[i] = cur[i];
		sort(res + 1, res + len + 1);
	} else if (Min * 2 + 1 == Max) {
		x = Min, y = Max;
		int k1 = 0, k2 = 0, k = 0;
		static int res1[MAXN], res2[MAXN];
		for (int i = 1; i <= len; i++)
			if (a[cur[i]] == Min || a[cur[i]] == Max) res1[++k1] = cur[i];
			else res2[++k2] = cur[i];
		sort(res1 + 1, res1 + k1 + 1, cnp);
		sort(res2 + 1, res2 + k2 + 1);
		int x1 = 1, x2 = 1;
		for (int i = 1; i <= len; i++)
			if (x1 <= k1 && x2 <= k2) {
				if (res1[x1] < res2[x2]) res[i] = res1[x1++];
				else res[i] = res2[x2++];
			} else {
				if (x1 <= k1) res[i] = res1[x1++];
				else res[i] = res2[x2++];
			}
		found = true;
		for (int i = len + 1; i <= n; i++)
			if (a[cur[i]] == Min) found = false;
		found |= (x1 == 0) || (a[res1[1]] == Max);
	}
	for (int i = 1; i <= len; i++)
		a[cur[i]]++;
	if (!valid || !found) res[1] = 0;
}
bool cmp(int *a, int *b) {
	int pos = 1;
	while (a[pos] == b[pos]) pos++;
	return a[pos] < b[pos];
}
bool check() {
	int Min = a[1], Max = a[1];
	for (int i = 2; i <= n; i++) {
		chkmin(Min, a[i]);
		chkmax(Max, a[i]);
	}
	return Min * 2 >= Max;
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		read(a[i]);
		s += a[i];
	}
	if (!check()) {
		puts("-1");
		return 0;
	}
	for (int i = 1; i <= n; i++)
		cur[i] = i;
	work(n);
	for (int i = 1; i <= n; i++) {
		m++, a[res[i]]--;
		cur[i] = res[i];
		ans[m] = res[i];
	}
	while (m != s) {
		static int inc[MAXN];
		memset(inc, 0, sizeof(inc));
		int len = 0; inc[1] = n + 1;
		for (int i = 1; i <= n; i++) {
			work(i);
			if (res[1] != 0 && cmp(res, inc)) {
				len = i;
				for (int j = 1; j <= i; j++)
					inc[j] = res[j];
			}
		}
		for (int i = 1; i + len <= n; i++)
			cur[i] = cur[i + len];
		for (int i = 1; i <= len; i++) {
			ans[++m] = inc[i];
			cur[n - len + i] = inc[i], a[inc[i]]--;
		}
	}
	for (int i = 1; i <= m; i++)
		printf("%d ", ans[i]);
	printf("\n");
	return 0;
}

Problem F. Forbidden Tournament

可以參考問題 Codeforces 1338E JYPnation

引理: 重複刪去圖中入度爲 11 的點,剩餘的圖是強連通的。並且,對於強連通的合法競賽圖,可以找到唯一的一條哈密爾頓迴路,使得每個點的出邊均指向其在迴路上的一段後繼。

關於引理的證明可以參考官方題解。

由此,枚舉刪去圖中入度爲 11 的點的次數,便只需要解決對於強連通的合法競賽圖的計數問題。
固定哈密爾頓迴路,並枚舉 11 號節點的出度 degdeg ,記 dpi,jdp_{i,j} 表示當前處理到節點 ii ,其出度爲 jij-i ,且此時方案仍然合法的方案數,樸素的轉移是 O(N)O(N) 的。

時間複雜度 O(N5)O(N^5)

#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;
}
int n, k, P, fac[MAXN], inv[MAXN];
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 binom(int x, int y) {
	if (y > x) return 0;
	else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void init(int n) {
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = 1ll * fac[i - 1] * i % P;
	inv[n] = power(fac[n], P - 2);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int solve(int n, int k) {
	int ans = 0;
	for (int deg = 1; deg <= k && deg <= n - 2; deg++) {
		static int dp[MAXN][MAXN];
		if ((n - 1) - deg > k) continue;
		memset(dp, 0, sizeof(dp)), dp[0][deg] = 1;
		for (int i = 0; i <= deg - 1; i++)
		for (int j = deg; j <= n - 1; j++) {
			int tmp = dp[i][j];
			for (int t = max(j, i + 2); t <= n - 1; t++) {
				int tnp = t - (i + 1);
				if (tnp <= k && (n - 1) - tnp <= k) update(dp[i + 1][t], tmp);
			}
		}
		for (int i = deg; i <= n - 1; i++)
			update(ans, dp[deg][i]);
	}
	return 1ll * ans * fac[n - 1] % P;
}
int main() {
	read(n), read(k), read(P);
	init(n); int ans = (k == n - 1) ? fac[n] : 0;
	for (int i = 3; i <= n; i++)
		update(ans, 1ll * fac[n] * inv[i] % P * solve(i, k - (n - i)) % P);
	cout << ans << endl;
	return 0;
}

用部分和優化轉移,時間複雜度 O(N4)O(N^4)

#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;
}
int n, k, P, fac[MAXN], inv[MAXN];
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 binom(int x, int y) {
	if (y > x) return 0;
	else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void init(int n) {
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = 1ll * fac[i - 1] * i % P;
	inv[n] = power(fac[n], P - 2);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int solve(int n, int k) {
	int ans = 0;
	for (int deg = 1; deg <= k && deg <= n - 2; deg++) {
		static int dp[MAXN][MAXN];
		if ((n - 1) - deg > k) continue;
		memset(dp, 0, sizeof(dp));
		dp[0][deg] = 1, dp[0][deg + 1] = P - 1;
		for (int i = 0; i <= deg - 1; i++)
		for (int j = deg; j <= n - 1; j++) {
			update(dp[i][j], dp[i][j - 1]);
			int tmp = dp[i][j], l = max(max(j, i + 2), n + i - k), r = min(n - 1, k + (i + 1));
			if (l <= r) update(dp[i + 1][l], tmp), update(dp[i + 1][r + 1], P - tmp);
		}
		for (int i = deg; i <= n - 1; i++) {
			update(dp[deg][i], dp[deg][i - 1]);
			update(ans, dp[deg][i]);
		}
	}
	return 1ll * ans * fac[n - 1] % P;
}
int main() {
	read(n), read(k), read(P);
	init(n); int ans = (k == n - 1) ? fac[n] : 0;
	for (int i = 3; i <= n; i++)
		update(ans, 1ll * fac[n] * inv[i] % P * solve(i, k - (n - i)) % P);
	cout << ans << endl;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章