【题意】
你们中的一些人可能玩过一个叫做消木块的游戏。
n个木块排成一列,每个木块都有一个颜色。
例如下图中木块的颜色分别为:金,银,银,银,银,铜,铜,铜,金。
每次,你都可以点击一个木块,这样被点击的木块以及和它相邻并且同色的木块就会消除。
如果一次性消除了k个木块,那么就会得到k*k分。
例如下图所示,点击银色木块,四个木块被消去,得到16分。
给定你一个游戏初始状态,请你求出最高得分是多少。
【输入格式】
第一行包含整数t,表示共有t组测试数据。
每组数据第一行包含整数n,表示共有n个木块。
第二行包含n个整数,表示n个木块的颜色。
代表木块颜色的整数范围是1~n。
【输出格式】
每组数据输出一个结果,每个结果占一行。
输出格式为“Case x: y”,其中x为数据组别编号,从1开始,y为结果。
【数据范围】
1≤t≤100
1≤n≤200
【输入样例】
2
9
1 2 2 2 2 3 3 3 1
1
1
【输出样例】
Case 1: 29
Case 2: 1
非常规区间DP
首先,我们可以把相同连续的数压成一个数。
然后,我们可以想到定义.
然而,对于两段颜色相同的,可以通过消去中间的数使得它们连续——这就使得问题有点棘手。
也就是说,一个区间的右端的数,既可以和右边的合并,也可以和左边的合并,但是合并的数的总个数又不一定。
由于我们迫切地期望知道多合并的数的个数,所以我们不妨加一个维度——定义(注意:这个得分是预支的,并不用考虑r右边的情况)。
当然,因为右端点可以和左边的合并,所以我们可以找到一个左边同色的位置来贡献答案:
状态转移方程:
上面的话有些难理解,需要耐心咀嚼
代码:
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
const int N=205;
void qr(int&x) {
char c=g;x=0;
while(!isdigit(c))c=g;
while(isdigit(c))x=x*10+c-'0',c=g;
}
int T,num,n,c[N],cnt[N],tot,pre[N],last[N],f[N][N][N];
inline int dfs(int l,int r,int k) {
int &x=f[l][r][k];
if(x>0) return x;
if(l>r) return 0;
x=dfs(l,r-1,0)+(cnt[r]+k)*(cnt[r]+k);
for(int pos=pre[r];pos>=l;pos=pre[pos])
x=max(x,dfs(l,pos,k+cnt[r])+dfs(pos+1,r-1,0));
return x;
}
int main() {
qr(T);
while(T--) {
memset(f,0,sizeof f);
memset(last,0,sizeof last);
tot=0; qr(n);
for(int i=1,x;i<=n;i++) {
qr(x);
if(x!=c[tot]) {
c[++tot]=x;
cnt[tot]=1;
}
else cnt[tot]++;
}
for(int i=1;i<=tot;i++) {
pre[i]=last[c[i]];
last[c[i]]=i;
}
printf("Case %d: %d\n",++num,dfs(1,tot,0));
}
return 0;
}