【AtCoder】AtCoder Grand Contest 046

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Takahashikun, The Strider

可以发现,任意时刻,玩家均位于以前两次操作路径的中垂线的交点上。
因此,答案即为使得玩家朝向与初始时第一次一致的时刻,即
360 g c d ( X , 360 ) \frac{360}{gcd(X,360)} gcd(X,360)360

时间复杂度 O ( L o g V ) O(LogV) O(LogV)

#include
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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

d p i , j   ( i ≥ A , j ≥ B ) dp_{i,j}\ (i\geq A, j\geq B) dpi,j (iA,jB) ,表示 C = i , D = j C=i,D=j C=i,D=j 时,问题的答案。
则显然有
d p A , j = A j − B dp_{A,j}=A^{j-B} dpA,j=AjB

对于 d p i , j   ( i > A , j ≥ B ) dp_{i,j}\ (i>A, j\geq B) dpi,j (i>A,jB) ,考虑枚举第一行第二个黑色的格子的位置,应当有
d p i , j = d p i − 1 , j × j + ∑ k = 1 j − 1 i k − 1 × ( j − k ) × d p i − 1 , j − k dp_{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} dpi,j=dpi1,j×j+k=1j1ik1×(jk)×dpi1,jk

用部分和优化转移,可以做到均摊 O ( 1 ) O(1) O(1)
时间复杂度 O ( C × D ) O(C\times D) O(C×D)

#include
using namespace std;
const int MAXN = 3e3 + 5;
const int P = 998244353;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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

将字符串中的 0 0 0 看做分隔符,记序列 a i a_i ai 表示相邻的两个 0 0 0 之间 1 1 1 的个数。
则一次操作相当于选择 ( i , j )   ( i < j ) (i,j)\ (i(i,j) (i<j) ,令 a i = a i + 1 , a j = a j − 1 a_i=a_i+1,a_j=a_j-1 ai=ai+1,aj=aj1

由于合法的序列 a i a_i ai 与合法的字符串一一对应,考虑对序列 a i a_i ai 计数。
则记 d p i , j , k dp_{i,j,k} dpi,j,k 表示决定了 a 1 , a 2 , … , a i a_1,a_2,\dots,a_i a1,a2,,ai ,其相较于原字符串总的增加量为 j j j ,进行了 k k k 次操作的方案数,显然有 O ( ∣ S ∣ ) O(|S|) O(S) 转移,则可以得到一个 O ( ∣ S ∣ 4 ) O(|S|^4) O(S4) 的做法。

用部分和优化转移,时间复杂度 O ( ∣ S ∣ 3 ) O(|S|^3) O(S3)

#include
using namespace std;
const int MAXN = 305;
const int P = 998244353;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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

考虑如何判断一个给定的字符串是否可以被生成。

令所判断的字符串为 T T T ,考虑 S , T S,T S,T 的最后一个字符:
若最后一个字符相同,则 S S S 能够生成 T T T 当且仅当删去一个字符的 S S S 可以生成删去一个字符的 T T T
若最后一个字符不同,则必须要通过给定的操作在此处插入一个字符。

对此判定过程设计状态 ( i , j , k ) (i,j,k) (i,j,k) ,表示当前 ∣ T ∣ = i |T|=i T=i ,需要通过操作插入 j j j 0 0 0 k k k 1 1 1
设计动态规划对各个状态的个数进行计数,显然有 O ( 1 ) O(1) O(1) 转移。

考虑如何判断状态 ( i , j , k ) (i,j,k) (i,j,k) 是否对应着可以生成的字符串 T T T 。则我们要求在仅对字符串的前 ∣ S ∣ − ( i − j − k ) |S|-(i-j-k) S(ijk) 位进行操作的情况下,可以向后插入 j j j 0 0 0 k k k 1 1 1

对于 S S S 中的前 2 2 2 个字符,我们可以选择进行一次操作,将其中一个字符插入到后面,也可以选则将任意一个字符插入到前 ∣ S ∣ − ( i − j − k ) |S|-(i-j-k) S(ijk) 个字符中,有了这样的字符,我们便可以选择操作 S S S 中的第 1 1 1 个字符和一个被事先插入的字符了。

因此,记 d p i , j , k dp_{i,j,k} dpi,j,k 表示对 S S S 的前 i i i 个字符进行操作,可以向后插入 j j j 0 0 0 k k k 1 1 1 的情况下,还可以向前 ∣ S ∣ − ( i − j − k ) |S|-(i-j-k) S(ijk) 个字符中至多可以插入的字符数,同样有 O ( 1 ) O(1) O(1) 转移。

时间复杂度 O ( ∣ S ∣ 3 ) O(|S|^3) O(S3)

#include
using namespace std;
const int MAXN = 305;
const int P = 998244353;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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 -1 1 当且仅当
2 × min ⁡ { a i } + 1 ≤ max ⁡ { a i } 2\times \min\{a_i\}+1\leq \max\{a_i\} 2×min{ai}+1max{ai}

对不不满足以上条件的情况,考虑出现次数最少的元素,不难构造一种方案。

由于我们需要确定字典序最小的解,我们还需要能够判定以给定排列 { p i } \{p_i\} {pi} 开头的的方案是否存在。那么,令 a i a_i ai 表示剩余各个元素的出现次数,若
2 × min ⁡ { a i } ≥ max ⁡ { a i } 2\times \min\{a_i\}\geq \max\{a_i\} 2×min{ai}max{ai}
则显然是存在方案的,而相比于刚开始的判定问题,不同的是,若
2 × min ⁡ { a i } + 1 = max ⁡ { a i } 2\times \min\{a_i\}+1=\max\{a_i\} 2×min{ai}+1=max{ai}
也是有可能存在方案的。

进一步地,此时,存在方案当且仅当在 { p i } \{p_i\} {pi} 中,使得 a i a_i ai 取到最大的 i i i 均出现在使得 a i a_i ai 取到最小的 i i i 的前面。由此,枚举接在 p i p_i pi 后的新排列的长度,我们可以贪心地找到能够拼接上去的,字典序最小的序列。

时间复杂度 O ( ( ∑ a i ) × K 2 L o g K ) O((\sum a_i)\times K^2LogK) O((ai)×K2LogK)

#include
using namespace std;
const int MAXN = 1005;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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 。

引理: 重复删去图中入度为 1 1 1 的点,剩余的图是强连通的。并且,对于强连通的合法竞赛图,可以找到唯一的一条哈密尔顿回路,使得每个点的出边均指向其在回路上的一段后继。

关于引理的证明可以参考官方题解。

由此,枚举删去图中入度为 1 1 1 的点的次数,便只需要解决对于强连通的合法竞赛图的计数问题。
固定哈密尔顿回路,并枚举 1 1 1 号节点的出度 d e g deg deg ,记 d p i , j dp_{i,j} dpi,j 表示当前处理到节点 i i i ,其出度为 j − i j-i ji ,且此时方案仍然合法的方案数,朴素的转移是 O ( N ) O(N) O(N) 的。

时间复杂度 O ( N 5 ) O(N^5) O(N5)

#include
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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 ( N 4 ) O(N^4) O(N4)

#include
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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;
}

你可能感兴趣的:(【OJ】AtCoder)