【BZOJ 4565】 [Haoi2016]字符合并 区间dp+状压
考试的时候由于总是搞这道题导致爆零~~~~~(神™倒序难度.....)
考试的时候想着想着想用状压,但是觉得不行又想用区间dp,然而正解是状压着搞区间,这充分说明了一件事,状压不是只是一种dp而是一种用用二进制表示状态的方法,之前打的状压dp只不过是在线性dp的时候用了这种方法。
我们发现对于一个固定长度的区间他最后缩成的位数是一定的(且属于1~k-1),而且最后的每一位的数字的来源相互独立因为他们分别完全展开之后无交。那么我们按照区间dp的一般思路,扩展长度转移状态,我们将转移来源分为两部分,设mid为中间点,mid左边贡献1位,mid右边贡献其他位,那么就可以转移了。
对于len∈[2,k-1],我们 f[i][j][s]=max(f[i][mid][s>>1],f[mid+1][j][s&1]),(f[i][j][s]在[i,j]区间上最后状态为s的最大收益)
对于len==1,我们知道出来长度为1时他的1都是缩出来的因此我们要先处理在这里(1<<k)的状态最后根据c和w再转移
注意枚举顺序!!!
#include <cstdio> typedef long long LL; const int MAXN=310; LL f[MAXN][MAXN][MAXN]; int n,k,full; int len[MAXN]; int a[MAXN],c[MAXN],w[MAXN]; const LL Inf=2305843009213693952LL; inline LL Max(LL x,LL y){ return x>y?x:y; } int main(){ scanf("%d%d",&n,&k); for(int i=1;i<k;i++)len[i]=i; for(int i=k;i<=n;i++)len[i]=len[i-k+1]; for(int i=1;i<=n;i++) scanf("%1d",&a[i]),f[i][i][a[i]]=0,f[i][i][a[i]^1]=-Inf; for(int i=0;i<(1<<k);i++) scanf("%d%d",&c[i],&w[i]); for(int l=2;l<=n;l++){ full=(1<<(len[l]==1?k:len[l])); for(int i=1,r=l;r<=n;i++,r++){ for(int j=0;j<full;j++){ f[i][r][j]=-Inf; for(int mid=r-1;mid>=i;mid-=k-1) f[i][r][j]=Max(f[i][r][j],f[i][mid][j>>1]+f[mid+1][r][j&1]); } if(len[l]==1){ LL g[2]={0,0}; for(int j=0;j<full;j++) g[c[j]]=Max(g[c[j]],f[i][r][j]+w[j]); f[i][r][1]=g[1]; f[i][r][0]=g[0]; } } } full=(1<<len[n]); LL ans=-Inf; for(int i=0;i<full;i++) ans=Max(ans,f[1][n][i]); printf("%lld",ans); }
苟利国家生死以, 岂因祸福避趋之。