参考:徐源盛《对一类动态规划问题的研究》以及刘汝佳的黑书《算法艺术与信息学竞赛》
将方块序列,按颜色分成一段一段的,例如 1 1 1 1 1 3 2 2 1 1 1 可记为color[ 1 ] = 1; len[1 ] = 5; color[ 2 ] = 3; len[ 2 ] =1; color[ 3 ] = 2; len[ 3 ] = 2; color[ 4 ] = 1; len[ 4 ] = 3;
(1) 如果直接消去第j个区域和未来会接到j后面的k块,那么f[i][j][k]=f[i][j-1][0]+(bj+k)*(bj+k)。
(2) 如果j与之前一起合并,假设与j颜色相同的是区域p,那么
f[i][j][k]=f[i][p][k+bj]+f[p+1][j-1][0](i<=p<j且color[ p ]==color[ j ])
f[i][j][k]为两者的最大值。 (注意:k为假设未来会有k个连在j的后面,并将这样的情况的值存储起来,如果未来真的发生这种情况,将这个值直接调用)
答案即是f[1][n][0]。
代码1:此代码感觉是有问题的,问题在注释中,但是可以AC
#include <iostream> using namespace std; #define MAX(a, b) a>b?a:b #define N 202 int color[N], len[N], after[N]; int dp[N][N][N]; int main() { int cas, m, n; int i, j, l, r, k, p, id; int seq[N], cnt; bool vis[N]; cas = 1; scanf("%d", &m); while(m--) { scanf("%d", &n); scanf("%d", &seq[1]); id = cnt = 1; for(i = 2; i <= n; i++){ scanf("%d", &seq[i]); if(seq[i] == seq[i-1]) cnt++; else { color[id] = seq[i-1]; len[id++] = cnt; cnt = 1; } } color[id] = seq[n]; len[id++] = cnt; memset(vis, false, sizeof(vis)); memset(after, -1, sizeof(after)); for(i = 1; i < id-2; i++){ //after[i] 的值为下一段与第i段颜色相同的下标 if(vis[i]) continue; int tmp = i; for(j = i+2; j < id; j++) if(color[tmp] == color[j]){ after[tmp] = j; tmp = j; vis[j++] = true; } } memset(dp, 0, sizeof(dp)); for(j = 1; j < id; j++){ //按长度遍历 for(l = 1; l+j-1 < id; l++){ r = l+j-1; for(i = after[r], k = 0;; i = after[i]){ //k为未来接在r后的方块数 dp[l][r][k] = dp[l][r-1][0]+(len[r]+k)*(len[r]+k); for(p = r-2; p >= l; p--) if(color[p] == color[r]) dp[l][r][k] = MAX(dp[l][r][k], dp[l][p][k+len[r]]+dp[p+1][r-1][0]); if(i == -1) break; else k += len[i]; //问题:k的取值为0,0+len[i1],..., 0+len[i1]+len[i2]+...一直加到某一段后面没有与之颜色相同的为止 //(其中i1为r后面的与r颜色相同的第一段的下标,i2为i1后面的与i1颜色相同的第一段的下标) //觉得这样的写法是有问题的,因为k的取值还可能是 0不加len[i1]而直接加上len[i2]等, //这样的k值我这样的写法是无法取到的 } } } printf("Case %d: %d\n", cas++, dp[1][id-1][0]); } return 0; }
代码2:这个代码没问题
#include <iostream> using namespace std; #define MAX(a, b) a>b?a:b #define N 202 int color[N], len[N], after[N]; int dp[N][N][N]; int main() { int cas, m, n; int i, j, l, r, k, p, id; int seq[N], cnt; bool vis[N]; cas = 1; scanf("%d", &m); while(m--) { scanf("%d", &n); scanf("%d", &seq[1]); id = cnt = 1; for(i = 2; i <= n; i++){ scanf("%d", &seq[i]); if(seq[i] == seq[i-1]) cnt++; else { color[id] = seq[i-1]; len[id++] = cnt; cnt = 1; } } color[id] = seq[n]; len[id++] = cnt; memset(vis, false, sizeof(vis)); for(i = id-1; i >= 1; i--){ /*此处after[i]记录i之后与i颜色相同的方块最多有多少个*/ if(vis[i]) continue; after[i] = 0; int tmp = i; for(j = i-2; j >= 1; j--) if(color[j] == color[tmp]){ after[j] = after[tmp] + len[tmp]; tmp = j; vis[j--] = true; } } memset(dp, 0, sizeof(dp)); for(j = 1; j < id; j++){ //按长度遍历 for(l = 1; l+j-1 < id; l++){ r = l+j-1; for(k = 0; k <= after[r]; k++){ //k为未来接在r后的方块数 dp[l][r][k] = dp[l][r-1][0]+(len[r]+k)*(len[r]+k); for(p = r-2; p >= l; p--) if(color[p] == color[r]) dp[l][r][k] = MAX(dp[l][r][k], dp[l][p][k+len[r]]+dp[p+1][r-1][0]); } } } printf("Case %d: %d\n", cas++, dp[1][id-1][0]); } system("pause"); return 0; }