Codeforces Round 972 (Div. 2)
C. Lazy Narek (C)
\(dp[i][j]\) 表示到第 \(i\) 个字符串为止、以"narek"中第 \(j\) 个字母结尾时的最大得分,预处理每个字符串从第 \(j\) 个字母开始统计所得的最大长度,暴力转移即可,时间复杂度 \(O(25n)\). vp的时候由于中途花了半小时选课、没调完这题,不好评价。
const int N = 1e3 + 10, INF = 1e9;
char c[N][N], a[5] = {'n', 'a', 'r', 'e', 'k'};
int sum[N], cnt[N][5], dp[5][2];
void solve() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%s", c[i] + 1);
sum[i] = 0;
for(int j = 0; j < 5; j++) {
cnt[i][j] = 0;
int pre = (j + 4) % 5;
for(int k = 1; k <= m; k++) {
if(c[i][k] == a[j]) sum[i]++;
if(c[i][k] == a[(pre + 1) % 5]) {
pre = (pre + 1) % 5;
cnt[i][j]++;
}
}
}
}
int it = 1;
for(int j = 1; j < 5; j++) dp[j][it] = -INF;
dp[0][it] = 0;
for(int i = 1; i <= n; i++) {
it ^= 1;
for(int j = 0; j < 5; j++) {
dp[j][it] = dp[j][it ^ 1];
}
for(int j = 0; j < 5; j++) {
for(int k = 0; k < 5; k++) {
//printf("cnt[%d] = %d\n", k, cnt[i][k]);
int d = j - k;
if(d < 0) d += 5;
if(d > cnt[i][k] || cnt[i][k] == 0) continue;
int to = ((cnt[i][k] - d) % 5 + j) % 5;
dp[to][it] = max(dp[to][it], dp[j][it ^ 1] + (cnt[i][k] - d) - (sum[i] - cnt[i][k] + d));
//printf("j = %d, to = %d, ori = %d, dp = %d\n", j, to, dp[j][it ^ 1], dp[to][it]);
}
}
}
int ans = 0;
for(int j = 0; j < 5; j++) {
ans = max(ans, dp[j][it] - j * 2);
}
printf("%d\n", ans);
}
D. Alter the GCD (D)
一个妙妙小结论:对于一个序列 \(a\),其所有前缀的gcd最多只有 \(\log a_{max}\) 个。用st表维护区间gcd,暴力枚举 \(1\) 至 \(n\) 作为左端点 \(l\),只有当 \(gcd(l, r)\) 或 \(gcd(r + 1, n)\) 发生变化时,才有可能出现新的答案,二分查找即可。
为什么没有代码呢?因为我的代码TLE了,并且官方题解太复杂没看懂,于是被迫放弃
E1. Subtangle Game (Easy Version) (E1)
首先有一个贪心的结论:若矩阵中存在多个 \((i, j) = a\),选择最接近 \((n, m)\) 的元素、使剩余矩阵中不存在 \(a\) 是最优的。(假设远离 \((n, m)\) 的某个 \((x, y)\) 与当前 \((i, j)\) 之间存在对下一轮选择更有利的位置,对手也可以使其无法选择。)因此 \(a\) 序列中重复出现的数字、及其之后的所有数实际上是无效的,由 \(a_i\leq 7\) 可得有效数字最多只有 \(7\) 个,考虑dp,\(dp[i][j][k]\) 表示对序列中第 \(i\) 个数、在 \((j, k)\) 至 \((n, m)\) 的子矩阵中先手操作的胜负情况。该状态可由 \(dp[i][j - 1][k],dp[i][j][k - 1]\)(必胜态)或 \(dp[i][j - 1][k - 1]\)(必败态)转移而来,根据博弈论规则计算即可。
void solve() {
int l, n, m;
scanf("%d%d%d", &l, &n, &m);
for(int i = 1; i <= l; i++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
scanf("%d", &b[i][j]);
}
}
int l1 = l;
set <int> s;
for(int i = 1; i <= l; i++) {
if(s.count(a[i])) {
l1 = i - 1;
break;
}
s.insert(a[i]);
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
for(int k = 1; k <= l1; k++) {
dp[k][i][j] = 0;
}
}
}
for(int k = l1; k; k--) {
for(int i = n; i; i--) {
for(int j = m; j; j--) {
if(i < n && dp[k][i + 1][j]) dp[k][i][j] = 1;
if(j < m && dp[k][i][j + 1]) dp[k][i][j] = 1;
if(b[i][j] == a[k]) {
if(i == n || j == m || k == l1 || dp[k + 1][i + 1][j + 1] == 0) dp[k][i][j] = 1;
}
}
}
}
if(dp[1][1][1]) printf("T\n");
else printf("N\n");
}
E2. Subtangle Game (Hard Version) (E2)
E1的状态转移在E2显然会TLE,考虑对原dp方程进行优化,将第一维的信息转到dp值中;具体地,令 \(dp[i][j]\) 表示使 \((i, j)\) 到 \((n, m)\) 子矩阵先手必胜的最长后缀起点。乍一看非常奇怪,但这和博弈论从终局向前推理的逻辑是一致的。对于不同的起点位置,其先后手顺序会发生变化,因此维护两个dp数组分别记录起点(在 \(a\) 序列中)为奇数/偶数的情况。若奇数情况下 \(dp[1][1] = 1\) 成立,则先手必胜,反之必败。
void solve() {
int l, n, m;
scanf("%d%d%d", &l, &n, &m);
for(int i = 1; i <= l; i++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
scanf("%d", &b[i][j]);
}
}
for(int i = 1; i <= n + 1; i++) {
for(int j = 1; j <= m + 1; j++) {
f[i][j] = g[i][j] = INF;
}
}
fill(id + 1, id + n * m + 1, 0);
for(int i = 1; i <= l; i++) {
if(id[a[i]]) {
l = i - 1;
break;
}
id[a[i]] = i;
}
for(int i = n; i; i--) {
for(int j = m; j; j--) {
// 常规转移
g[i][j] = min(g[i + 1][j], g[i][j + 1]);
f[i][j] = min(f[i + 1][j], f[i][j + 1]);
if(!id[b[i][j]]) continue;
// 偶数情况,奇数必胜态在至少3步后,此时操作则奇数必败
if((id[b[i][j]] & 1) == 0 && id[b[i][j]] + 3 <= g[i + 1][j + 1]) {
f[i][j] = min(f[i][j], id[b[i][j]]);
}
// 奇数情况同理
if((id[b[i][j]] & 1) && id[b[i][j]] + 3 <= f[i + 1][j + 1]) {
g[i][j] = min(g[i][j], id[b[i][j]]);
}
}
}
if(g[1][1] == 1) printf("T\n");
else printf("N\n");
}