[HAOI2016]字符合并

一个长为 \(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 常数小,可通过。

一些注意事项:

  1. LL
  2. 枚举 \(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;
}
上一篇:[loj3640]细菌


下一篇:ABC235题解