bzoj4565 [Haoi2016]字符合并
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=4565
题意:
有一个长度为 n 的 01 串,你可以每次将相邻的 k 个字符合并,得到一个新的字符并获得一定分数。得到的新字符和分数由这 k 个字符确定。
你需要求出你能获得的最大分数。
数据范围
1<=n<=300,0<=ci<=1,k<=8,输入的1<=wi<=10^9
题解:
dalao的题解
n很小,容易想到 N^3的区间DP,k很小,考虑状压。(那么就要能合并则合并,长度一直<=k)
dp[i][j][S]表示把 [i,j]区间压成 S状态的最大值。
如何解决 0001,001,01的表示混淆的情况?
仔细想想,发现当区间长度一定时,它能压成的01串长度也是一定的。(之前自己的想法是再前面加个1表示长度限制)
因此我们只需要只从合法的转移过来。
当S转移到S<<1时,要保证f[i][j][S<<1]合法,且f[m][j][0],0表示的确实只有一个数。
因此,枚举m是每k-1个一跳。
([m,j]长度 k,k+k-1,k+k-1+k-1…)
于是
f[i][j][S<<1]=max(f[i][j][S<<1],f[i][m-1][S]+f[m][j][0])
f[i][j][S<<1|1]=max(f[i][j][S<<1|1],f[i][m-1][S]+f[m][j][1])
从长度短的区间到长度长的区间,一层一层地求出值。
对于k个合并成一个数的情况,由当层的dp[i][j][S] ((j-i)%(k-1)==0)加上对应的W[S]转移过来。
注意dp[i][j][0/1]表示的0长度不是1,因此不能直接更新到dp[i][j][0/1]中,要新开一个来存,最后再更新。
g[c[S]]=max(g[c[S]],f[i][j][S]+w[S])
f[i][j][0]=g[0] f[i][j][1]=g[1]
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
const int N=305;
int n,k,w[1<<8],c[1<<8],top,s[N];
LL dp[N][N][1<<8],_inf;
int main()
{
scanf("%d%d",&n,&k);
top=(1<<k)-1;
for(int i=1;i<=n;i++)
scanf("%1d",&s[i]);
for(int i=0;i<=top;i++)
scanf("%d%d",&c[i],&w[i]);
memset(dp,128,sizeof(dp));//-inf
_inf=dp[0][0][0];
for(int i=1;i<=n;i++) dp[i][i][s[i]]=0;
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
for(int m=j;m>i;m-=(k-1))
{
for(int S=0;S<=top;S++)
{
if(dp[i][m-1][S]!=_inf)
{
if(dp[m][j][0]!=_inf) dp[i][j][S<<1]=max(dp[i][j][S<<1],dp[i][m-1][S]+dp[m][j][0]);
if(dp[m][j][1]!=_inf) dp[i][j][S<<1|1]=max(dp[i][j][S<<1|1],dp[i][m-1][S]+dp[m][j][1]);
}
}
}
if(len-1>=k-1&&(len-1)%(k-1)==0) //可以缩成一个
{
LL g[2];
g[0]=g[1]=_inf;
for(int S=0;S<=top;S++)
if(dp[i][j][S]!=_inf) g[c[S]]=max(g[c[S]],dp[i][j][S]+w[S]);
dp[i][j][0]=g[0]; dp[i][j][1]=g[1];
}
}
}
LL ans=0;
for(int i=0;i<=top;i++)
ans=max(ans,dp[1][n][i]);
printf("%I64d\n",ans);
return 0;
}