为了方便将原题题意中的k改成了m(为什么方便??)
毒瘤有趣理由:因为毒瘤,主要在于分析
Description
有一个长度为 n 的 01 串,你可以每次将相邻的 m 个字符合并,得到一个新的字符并获得一定分数。得到的新字符和分数由这 m 个字符确定。你需要求出你能获得的最大分数。
Input
第一行两个整数n,m。接下来一行长度为n的01串,表示初始串。接下来2^k行,每行一个字符ci和一个整数wi,ci表示长度为k的01串连成二进制后按从小到大顺序得到的第i种合并方案得到的新字符,wi表示对应的第i种方案对应获得的分数。
1<=n<=300,0<=ci<=1,wi>=1,m<=8
Solution
首先观察到m很小,n只有300。这种区间操作的题目,很容易想到区间dp,由于合并后会产生新的字符,造成一定的影响,所以考虑多加1维状态。于是就有了
由于我们考虑的是区间最优,所以
必定不会大于
大概写个转移,考虑s的最后一位是由哪一段区间得到的
这个转移是不完整的,并且如果挨个枚举k超时的可能性很大
接下来考虑一个问题,多少个连续字符可以合并成1个字符:
1个字符可以不操作,m个连续字符可以合并成1个字符.于是得到 个连续字符,也能合并成一个字符。
所以当
时有这样一种转移,即这一段区间也可以合并成一个字符(在实际实现的时候,需要开临时数组来转移)
又想到之前的转移,枚举
,使区间
合并成一个字符。
从r开始,由于上述结论,k每次可以-(m-1)
Code
#include <bits/stdc++.h>
using namespace std;
int n,m,a[310],c[300],w[300];
long long f[310][310][300];
char S[310];
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",S+1);
for (int i=1;i<=n;i++)
a[i]=int(S[i]-48);
for (int i=0;i<(1<<m);i++)
scanf("%d%d",&c[i],&w[i]);
memset(f,-10,sizeof(f));
for (int i=1;i<=n;i++) f[i][i][a[i]]=0;
for (int len=2;len<=n;len++)
for (int l=1;l<=n-len+1;l++) {
int r=len+l-1;
for (int s=0;s<(1<<m);s++)
for (int k=r;k>l;k-=(m-1))
f[l][r][s]=max(f[l][r][s],f[l][k-1][s>>1]+f[k][r][s&1]);
if ((len-1)%(m-1)==0) {
long long g[2];
g[0]=g[1]=-1e12;
for (int s=0;s<(1<<m);s++)
g[c[s]]=max(g[c[s]],f[l][r][s]+w[s]);
f[l][r][0]=g[0];f[l][r][1]=g[1];
}
}
long long ans=0;
for (int i=0;i<(1<<m);i++)
ans=max(ans,f[1][n][i]);
cout<<ans;
return 0;
}