思路
这道题思路很巧妙,一般的区间DP都是设
f
[
i
]
[
j
]
f[i][j]
f[i][j] 为
i
i
i 到
j
j
j 的最大值
但是这道题你会发现j之后的
k
k
k 个和
j
j
j 相同的木块会对答案有贡献,而你又不能拆成几个区间搞,
所以就多维护一个
k
k
k,设
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k] 表示,
i
i
i 到
j
j
j 区间后面有
k
k
k 个与
j
j
j 相同的木块的最高得分。
然后也要分情况讨论:
⭐首先是我们可以不合并
j
j
j 与
k
k
k ,让
[
i
,
j
]
[i,j]
[i,j] 消除,把区间
k
k
k 单独消除.
⭐还有一种情况是我们寻找到一个中转点
m
m
m,先让
[
m
+
1
,
j
−
1
]
[m+1,j-1]
[m+1,j−1] 消除,然后把
[
i
,
m
]
[i,m]
[i,m] 以及
j
j
j 和
k
k
k 一起消除。(
[
m
+
1
,
j
−
1
]
[m+1,j-1]
[m+1,j−1] 消除了,
[
i
,
m
]
[i,m]
[i,m] 就和
j
j
j 并在一起了)
这样做会发现很像递归,然后就变成记忆化搜索去搞。
你会发现直接40分超时,观察发现,这个相同的木块一个一个搞很烦,所以就可以合并成一个木块搞(就像缩点,带权)。这样就可以节省一些时间复杂度。
代码
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
long long T,n,a,len[10010],c[10010],last,cnt;
long long f[211][211][211];
long long solve(long long l,long long r,long long k)
{
if(f[l][r][k])
return f[l][r][k];
if(l==r)
return (len[l]+k)*(len[l]+k);
f[l][r][k]=solve(l,r-1,0)+(len[r]+k)*(len[r]+k);
for(long long i=l; i<r-1; i++)
if(c[i]==c[r])
f[l][r][k]=max(f[l][r][k],solve(l,i,len[r]+k)+solve(i+1,r-1,0));
return f[l][r][k];
}
int main()
{
scanf("%lld",&T);
long long sb=0;
while(T--)
{
cnt=0,sb++,last=0;
scanf("%lld",&n);
memset(f,0,sizeof(f));
memset(c,0,sizeof(c));
memset(len,0,sizeof(len));
for(long long i=1; i<=n; i++)
{
scanf("%lld",&a);
if(a==last)
len[cnt]++;
else
{
c[++cnt]=a;
len[cnt]++;
}
last=a;
}
printf("Case %lld: %lld\n",sb,solve(1,cnt,0));
}
return 0;
}