POJ1390 Blocks
题目链接:POJ 1390.Blocks
题意:
有\(n\)个方块排成一列,每个方块有颜色即\(1\)到\(n\)的一个值,每次操作可以把一段相同颜色的方块拿走,长度为\(k\),则获得的分数为 \(k\times k\),求可获得的分数最大值。
其中每次要把一段全部拿走,不能只取其中一部分。
每个测试点T组数据,\(T\leq15\) , \(n\leq200\) 。
想了好一会。。
思路:
根据题意这显然是\(O(n^3)\)的区间DP,可以发现仅设两个维度\(l\),\(r\)不足以转移信息,考虑在每次合并两个区间时,我们需要知道区间各自最后的颜色和最后该颜色方块的数量(以维护将两端合一起拿的情况),而根据复杂度我们只能记录一个量,而且在转移的时候不能枚举前继状态的该量(否则就至少\(O(n^4)\)了),我们发现,此时条件不允许我们关注区间内部的状态,因此只能观察外部的情况。
考虑一个色段从外面被合起来拿走,根据DP不重不漏的原则,我们只看最右边的色段的情况,它被合着拿走的前提是此色段和外边同色的色段中间的方块已被取走,我们只需要关注他们的总数为多少以计算分数,这样子,设计状态\(dp[l][r][k]\)为在区间\([l,r]\)上,同时在\(a[r]\)右侧还有\(k\)个与\(a[r]\)同色的方块等着和左边合并,此种状态下它被全部取走所能获得的最大分数。
我们先将所有色段统计大小后缩成格子,对于\(a[r]\),它有两种选择:
- 和右边的\(k\)个格子合并(题意要求必须全部合并),\(c[r]\)为此格原来色段的长度
\[dp[l][r][k]=dp[l][r-1][0]+(c[r]+k)*(c[r]+k)
\]
- 和自己区间内的同色格子a[i]合并(自己加入等待合并的色段中),中间跳过的区间就没有等待队伍了,\(k=0\)。
\[dp[l][r][k]=dp[l][i][c[r]+k]+dp[i+1][r-1][0]
\]
预处理\(nxt[i]\)为在\(a[i]\)往前第一个和其同色的格子下标,\(rem[i][j]\)为从\(a[j]\)的下一位往后,颜色为\(i\)的方块数量。
上代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#define N 210
using namespace std;
int dp[N][N][N],a[N],c[N];
int rem[N][N],nxt[N],lst[N];
int n,m;
bool Max(int &a,int b){
if(a>=b)return false;
a=b;return true;
}
void init(){
memset(rem,0,sizeof(rem));
memset(lst,-1,sizeof(lst));
cin>>n;
int last=0; m=0;
for(int i=0;i<n;i++){
cin>>a[i];
if(last==a[i]){c[m-1]++;continue;}
a[i]--;
c[m]=1,a[m]=a[i];
nxt[m++]=lst[a[i]];
lst[a[i]]=m-1;
last=a[i]+1;
}
for(int i=0;i<n;i++){
for(int j=m-2;j>=0;j--)
rem[i][j]=rem[i][j+1]+(i==a[j+1]?c[j+1]:0);
}
}
int main(){
int t;
cin>>t;
for(int o=1;o<=t;o++){
init();
for(int len=1;len<=m;len++)
for(int l=0,r=len-1;r<m;l++,r++)
for(int k=0;k<=rem[a[r]][r];k++){
dp[l][r][k]=(c[r]+k)*(c[r]+k);
if(l!=r)dp[l][r][k]+=dp[l][r-1][0];
for(int i=nxt[r];~i&&i>=l;i=nxt[i]){
Max(dp[l][r][k],dp[l][i][c[r]+k]+dp[i+1][r-1][0]);
}
}
printf("Case %d: %d\n",o,dp[0][m-1][0]);
}
return 0;
}