一个长为 \(1\le n\le 300\) 的 01 串 \(a\),可以选长为 \(2\le k\le 8\) 的连续段 \([l,r]\) 替换为 \(c_x\in \{0,1\}\),得到 \(1\le w_x\le 10^9\) 价值,其中 \(x\) 表示 \(a:[l,r]\) 构成的二进制数。例如
10011
将中间三个字符替换为1
,会得到新字符串111
,而后续的操作均在新字符串上进行。
受到启发
- 看到 \(k\le 8\) 想到状态压缩。
- 看到 \(n\le 300\) 想到区间 dp。
观察性质
- 将一个区间从始态到终态的过程本质是将区间划分成 \(<k\) 段,每一段中的数合并成一个数。
- 由于 \(w_x\le 1\),对于一固定长度区间,它最后的长度是确定的,因一次操作将区间长削减 \(k-1\)。为
y=len%(k-1)==0?(k-1):len%(k-1)
。
题解
\(f[l,r,s]\) 表示区间 \([l,r]\) 的终态为 \(s\) 的最大价值。\(s\) 是一个 y
位二进制数,y
的含义见上。
枚举 \(s\) 的最后一位所代表的区间后缀的起始点 \(i>l\),\(f[l,r,s]=f[l,i-1,s>>1]+f[i,r,s\&1]\)。
\(O(n^32^y)\le O(n^32^k)\)。
观察到有效地 \(i\) 只可能是 \(r,r-(k-1),r-2(k-1),...\),这样枚举可将常数 \(\div k\)。
\(O(n^32^k/k)\)。区间 dp 常数小,可通过。
一些注意事项:
- LL
- 枚举 \(s\) 时,如果
y==1
,发现没法转移。考虑倒数第二次转移的末态作为 \(s\) 来。即y+=(k-1)
。但储存f[l,r,s]
时的 \(s\) 还是得按最后一次的 \(s\) 来表示。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=305;
int n,k,ans,a[N],c[260],v[260],f[N][N][260];
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=0;i<(1<<k);i++)cin>>c[i]>>v[i];
memset(f,-0x3f,sizeof(f));
for(int i=1;i<=n;i++)f[i][i][a[i]]=0;
for(int l=1;l+k-1<=n;l++){
int x=0;
for(int i=l;i<=l+k-1;i++)x=(x<<1)+a[i];
f[l][l+k-1][c[x]]=v[x];
}
for(int len=2;len<=n;len++){
for(int l=1,r=len;r<=n;l++,r++){
int x=len%(k-1);if(!x)x+=k-1;if(x<=1)x+=k-1;
for(int s=0;s<(1<<x);s++){
int _s=x==k?c[s]:s;
int tmp=f[0][0][0];
for(int i=r;i>l;i-=k-1)
if(f[l][i-1][s>>1]!=f[0][0][0]&&f[i][r][s&1]!=f[0][0][0])
tmp=max(tmp,f[l][i-1][s>>1]+f[i][r][s&1]);
if(x==k&&tmp!=f[0][0][0])tmp+=v[s];
f[l][r][_s]=max(f[l][r][_s],tmp);
if(len==n)ans=max(ans,f[l][r][_s]);
}
}
}
cout<<ans;
}