[HAOI2016]字符合并

一个长为 1n300 的 01 串 a,可以选长为 2k8 的连续段 [l,r] 替换为 cx{0,1},得到 1wx109 价值,其中 x 表示 a:[l,r] 构成的二进制数。例如 10011 将中间三个字符替换为 1,会得到新字符串 111,而后续的操作均在新字符串上进行。

受到启发

  • 看到 k8 想到状态压缩。
  • 看到 n300 想到区间 dp。

观察性质

  • 将一个区间从始态到终态的过程本质是将区间划分成 <k 段,每一段中的数合并成一个数。
  • 由于 wx1,对于一固定长度区间,它最后的长度是确定的,因一次操作将区间长削减 k1。为 y=len%(k-1)==0?(k-1):len%(k-1)

题解

f[l,r,s] 表示区间 [l,r] 的终态为 s 的最大价值。s 是一个 y 位二进制数,y 的含义见上。
枚举 s 的最后一位所代表的区间后缀的起始点 i>lf[l,r,s]=f[l,i1,s>>1]+f[i,r,s&1]
O(n32y)O(n32k)

观察到有效地 i 只可能是 r,r(k1),r2(k1),...,这样枚举可将常数 ÷k
O(n32k/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;
}
posted @   pengyule  阅读(50)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示