消除木块 / 方块消除 Blocks
题目链接:ybt高效进阶5-2-3 / luogu UVA10559
题目大意
给你一些数,每次你可以选一个数,它和它旁边一样的数就会被消掉,你会获得的的分数是这次消掉数的个数的平方。
要你最大化分数。
思路
看到这个什么消掉一个区间的,不难想到用区间 DP。
由于它分数是每次消掉数的平方,那只设
f
i
,
j
f_{i,j}
fi,j 为删掉
i
,
j
i,j
i,j 区间的最大贡献似乎不太好做。
那我们再设
f
i
,
j
,
k
f_{i,j,k}
fi,j,k 为删掉
i
,
j
i,j
i,j 区间加上后面
k
k
k 个与
a
j
a_j
aj 相同的数组成的区间的最大贡献。
然后再想,你完全可以把数列中连续的相同的数合并成一个数,然后数带上长度。
这样,相邻的两个数之间都不同,就不用考虑一删删几个的情况了。
然后考虑 DP,那有两种情况,要么是直接
a
j
a_j
aj 和后面
k
k
k 个直接消掉,要么就是这些和前面的某段相同颜色的消掉。
那第一种情况,就是
f
i
,
j
−
1
,
0
+
(
n
u
m
j
+
k
)
2
f_{i,j-1,0}+(num_j+k)^2
fi,j−1,0+(numj+k)2。
第二种情况,我们就枚举
i
∼
j
i\sim j
i∼j,找
a
l
=
a
j
a_l=a_j
al=aj,对于每个找到的位置,分数就是
f
i
,
l
,
k
+
n
u
m
j
+
f
l
+
1
,
j
−
1
,
0
f_{i,l,k+num_j}+f_{l+1,j-1,0}
fi,l,k+numj+fl+1,j−1,0
那这些就去最大值。
可以先枚举区间长度,然后在枚举区间和
k
k
k,但我这里用的是记忆化搜索。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int T, n, a[201], f[201][201][201];
int na[201], nm[201], nn, tmp;
int work(int i, int j, int k) {
if (f[i][j][k]) return f[i][j][k];
if (i == j) return (nm[i] + k) * (nm[i] + k);
f[i][j][k] = work(i, j - 1, 0) + (nm[j] + k) * (nm[j] + k);//直接暴力构成
for (int mid = i; mid < j; mid++)
if (na[mid] == na[j])//拆成两段,先删完中间的,再把两边同颜色的一起删掉
f[i][j][k] = max(f[i][j][k], work(i, mid, k + nm[j]) + work(mid + 1, j - 1, 0));
return f[i][j][k];
}
int main() {
scanf("%d", &T);
while (T--) {
memset(f, 0, sizeof(f));
nn = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
na[++nn] = a[1];
nm[nn] = 1;
for (int i = 2; i <= n; i++)
if (a[i] == a[i - 1]) nm[nn]++;
else {
na[++nn] = a[i];
nm[nn] = 1;
}
printf("Case %d: %d\n", ++tmp, work(1, nn, 0));
}
return 0;
}