有一个长度为 n 的 01 串,你可以每次将相邻的 k 个字符合并,得到一个新的字符并获得一定分数。得到的新字符和分数由这 k 个字符确定。你需要求出你能获得的最大分数。
题解:
当初想这道题在转移方面想了半天,看了题解才知道原来自己还没有掌握区间DP的精髓。。
首先不难发现一个区间的最大分数一定是压缩到最短的时候,这个时候的长度是
(modk−1)
下的长度,设
f[l][r][o]
表示
[l,r]
区间压缩为
o
状态的最大分数,那么转移只需考虑把原始区间分为两段,两段拼接成
但是如何DP?有一种思路是直接枚举区间与状态,然后两段的状态随之确定。但是有一个问题是可能是两段的状态合并之后再压缩成当前的状态。其实仔细分析根本不用考虑合并的状态,只需把转移锁定到状态的最后一位即可:
1.若该区间压缩后的长度不为1,那么该区间的最后状态 o1 的最后一位一定是由该区间末尾的某一段压缩而成。直接枚举是哪一段就好了。
2.若该区间压缩后的长度为1,那么考虑枚举长度为k的状态,之后再枚举一遍每种状态压缩后的分数,取最大值即可。
#include<bits/stdc++.h>
using namespace std;
const long long Maxn=305,LIM=(1<<8)+20;
inline long long read(){
char ch=getchar();long long i=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
return i*f;
}
long long n,k,f[Maxn][Maxn][LIM];
char ch[Maxn];
vector<long long>val;
vector<long long>c;
int main(){
memset(f,0xc0,sizeof(f));
n=read(),k=read();
scanf("%s",ch+1);
for(long long i=0;i<(1<<k);i++)c.push_back(read()),val.push_back(read());
for(long long l=1;l<=n;l++){
for(long long i=1;i<=n;i++){
if(i+l-1>n)break;
long long t=l%(k-1);
if(!t)t+=(k-1);
if(t==l){
long long res=0;
for(long long j=i;j<=i+l-1;++j){
res<<=1;
res^=(ch[j]=='1'?1:0);
}
f[i][i+l-1][res]=0;
}else{
long long S;
(t==1)?(S=(1<<k)-1):(S=(1<<t)-1);
for(long long o=0;o<=S;o++){
for(long long j=i+l-1;j>i;j-=(k-1)){
f[i][i+l-1][o]=max(f[i][i+l-1][o],f[i][j-1][o>>1]+f[j][i+l-1][o&1]);
}
}
if(t==1){
static long long g[2];
g[0]=g[1]=-0x3f3f3f3f;
for(long long o=0;o<=S;o++){
g[c[o]]=max(g[c[o]],f[i][i+l-1][o]+val[o]);
}
f[i][i+l-1][0]=g[0],f[i][i+l-1][1]=g[1];
}
}
}
}
long long t=n%(k-1);if(!t)t=k-1;
long long S=(1<<t)-1,ans=0;
for(long long o=0;o<=S;o++)ans=max(ans,f[1][n][o]);
printf("%lld\n",ans);
}