POJ1390 Blocks
题意描述
题意:
给定一个数列,每次可以消除一段相等的数,得分为这一段长度的平方,求最大得分。
看上去就很区间 DP,其实可以写的很简洁的。
算法分析
首先将数据根据颜色不同分为 \(n\) 段,\(a(i)\) 记录其颜色,\(b(i)\) 记录其长度。
一般普通区间 DP 的状态为 \(f(l,r)\) 表示区间 \([l,r]\) 消除的最大得分,可惜这并不能满足我们的要求。
原因可以看题目中给出的图,我们发现可能可以先消除中间的某些小段,然后一起消除大段可以得到更优解。
如果朴素枚举哪些段先消去,单次转移的复杂度就达到了 \(O(2^n)\),不太行。
为了使转移复杂度更优,我们考虑让状态表示更加复杂。
令 \(f(l,r,len)\) 表示区间 \([l,r]\),其中 \(r\) 后面还有 \(len\) 个与其颜色相同的木块,消除的最大得分。
有了新的一维状态,我们可以轻松地写出状态转移方程:
\[f(l,r,len) = \max_{l\leq k<r,a(k)=a(r)}\{f(l,k,len+b(r))+f(k+1,r-1,0)\}
\]
当然,我们也可以直接消去最右边的块,将剩余部分作为子问题:
\[f(l,r,len)=\max\{f(l,r-1,0) + (b(r)+len)^2\}
\]
为了保证代码的简洁性,可以考虑记忆化搜索的处理方式,显然有:
-
初始状态:\(f(i,i,len)=(b(i)+len)^2\)。
-
目标状态:\(f(1,n,0)\)。
代码实现
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210;
int T, n;
int a[N], b[N];
int f[N][N][N];
int read(){
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
return x*f;
}
int dfs(int l, int r, int len){
if(l == r)
return f[l][r][len] = (b[l] + len) * (b[l] + len);
if(f[l][r][len] != -1)
return f[l][r][len];
int mx = dfs(l, r - 1, 0) + (b[r] + len) * (b[r] + len);
for(int k = l; k < r; k ++)
if(a[k] == a[r])
mx = max(mx, dfs(l, k, len + b[r]) + dfs(k + 1, r - 1, 0));
return f[l][r][len] = mx;
}
int main(){
T = read();
int C = 0;
while(T --){
memset(f, -1, sizeof(f));
int m = read(); n = 0;
int last = 0;
for(int i=1; i<=m; i++){
int x = read();
if(x == last) b[n] ++;
else a[++n] = x, b[n] = 1, last = x;
}
printf("Case %d: %d\n", ++C, dfs(1, n, 0));
}
return 0;
}
简单分析一下时间复杂度:
所有状态空间为 \(O(n^3)\),单次转移复杂度 \(O(n)\),总时间复杂度 \(O(n^4)\)。
但是每次转移时,需要保证 \(a(k)=a(r)\),所以绝对跑不满这个上界,实测可以通过。
完结撒花。