BZOJ4565: [Haoi2016]字符合并(区间DP+状压DP)

传送门

有一个长度为 n 的 01 串,你可以每次将相邻的 k 个字符合并,得到一个新的字符并获得一定分数。得到的新字符和分数由这 k 个字符确定。你需要求出你能获得的最大分数。

题解:
当初想这道题在转移方面想了半天,看了题解才知道原来自己还没有掌握区间DP的精髓。。

首先不难发现一个区间的最大分数一定是压缩到最短的时候,这个时候的长度是 (modk1) 下的长度,设 f[l][r][o] 表示 [l,r] 区间压缩为 o 状态的最大分数,那么转移只需考虑把原始区间分为两段,两段拼接成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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值