祖玛游戏,两种颜色的球,每次可以选定一个长度为$k$的区间,区间内球同色,消除这个区间。消除后左右两边合并,但不会继续消除。求最小操作次数。
考虑对于一个区间,最后一个消灭的肯定是最左边的一些同色球。
为什么是这样?
首先如果只有这个区间,那么最左边永远可以指定作为最后一次消除:因为消除它不会对右边产生影响。
可能想不到的是,如果先消除左端点,右边就能跟区间外的合并,这样是不是不优了?
答案是否定的:先消除左端点,把右边合并到左边区间,可以把左端点这一段划到左边区间去,那么还是相当于右边成为了新的左端点。
那么$f[i][j][k]$表示$i,j$区间,消完剩余$k$个与左端点同色的,需要的次数。
$g[i][j]$表示消完$i,j$区间,需要的次数。
转移方程:
$f[i][j][k] = min{f[i][x][k] + g[k+1][j]}$
当$a[i]==a[j]$时有额外转移:$f[i][j][k]=min{f[i][j-1][k-1]}$
对于$g$数组,$g[i][j] = min{f[i][j][k]+1, g[i][x]+f[x+1][j]}$
代码如下:
#include <bits/stdc++.h> #define Mid ((l + r) >> 1) #define lson (rt << 1) #define rson (rt << 1 | 1) using namespace std; int read(){ char c; int num, f = 1; while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; return f * num; } int n, k, f[109][109][109], g[109][109], sum1[109], sum2[209]; char c[109]; signed main() { n = read(); k = read(); scanf("%s", c + 1); memset(f, 0x3f, sizeof(f)); memset(g, 0x3f, sizeof(g)); for(int i = 1; i <= n; i++) { sum1[i] = sum1[i - 1] + c[i] == 'G'; sum2[i] = sum2[i - 1] + c[i] == 'H'; } /* for(int i = 1; i <= n; i++) for(int j = i; j <= n; j++) if(sum1[j] - sum1[i - 1] == 0 || sum2[j] - sum2[i - 1] == 0) g[i][j] = 1; */ for(int i = 1; i <= n; i++) f[i][i][1] = 0; for(int len = 1; len <= n; len++) { for(int i = 1; i + len - 1 <= n; i++) { int j = i + len - 1; for(int q = 1; q <= len; q++) { for(int p = i; p + 1 <= i + len - 1; p++) { f[i][j][q] = min(f[i][j][q], f[i][p][q] + g[p + 1][j]); } if(c[i] == c[i + len - 1] && q > 1 && j - 1 >= i) f[i][j][q] = min(f[i][j][q], f[i][j - 1][q - 1]); if(q >= k) g[i][j] = min(g[i][j], f[i][j][q] + 1); } } for(int i = 1, j = i + len - 1; i + len - 1 <= n; i++) for(int p = i; p + 1 <= i + len - 1; p++) g[i][j] = min(g[i][j], g[i][p] + g[p + 1][j]); } printf("%d ", g[1][n] == 0x3f3f3f3f ? -1 : g[1][n]); return 0; }