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

本文介绍了解决BZOJ4565[Haoi2016]字符合并问题的一种动态规划方法。通过区间DP与状态压缩技术,在小规模数据范围内实现了高效求解最大得分的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

bzoj4565 [Haoi2016]字符合并

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=4565

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

数据范围
1<=n<=300,0<=ci<=1,k<=8,输入的1<=wi<=10^9

题解:
dalao的题解

n很小,容易想到 N^3的区间DP,k很小,考虑状压。(那么就要能合并则合并,长度一直<=k)

dp[i][j][S]表示把 [i,j]区间压成 S状态的最大值。

如何解决 0001,001,01的表示混淆的情况?
仔细想想,发现当区间长度一定时,它能压成的01串长度也是一定的。(之前自己的想法是再前面加个1表示长度限制)
因此我们只需要只从合法的转移过来
当S转移到S<<1时,要保证f[i][j][S<<1]合法,且f[m][j][0],0表示的确实只有一个数。
因此,枚举m是每k-1个一跳。
([m,j]长度 k,k+k-1,k+k-1+k-1…)
于是
f[i][j][S<<1]=max(f[i][j][S<<1],f[i][m-1][S]+f[m][j][0])
f[i][j][S<<1|1]=max(f[i][j][S<<1|1],f[i][m-1][S]+f[m][j][1])
从长度短的区间到长度长的区间,一层一层地求出值。

对于k个合并成一个数的情况,由当层的dp[i][j][S] ((j-i)%(k-1)==0)加上对应的W[S]转移过来。
注意dp[i][j][0/1]表示的0长度不是1,因此不能直接更新到dp[i][j][0/1]中,要新开一个来存,最后再更新。
g[c[S]]=max(g[c[S]],f[i][j][S]+w[S])
f[i][j][0]=g[0]   f[i][j][1]=g[1]

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
const int N=305;
int n,k,w[1<<8],c[1<<8],top,s[N];
LL dp[N][N][1<<8],_inf;
int main()
{
    scanf("%d%d",&n,&k);
    top=(1<<k)-1;
    for(int i=1;i<=n;i++)
    scanf("%1d",&s[i]);
    for(int i=0;i<=top;i++)
    scanf("%d%d",&c[i],&w[i]);

    memset(dp,128,sizeof(dp));//-inf
    _inf=dp[0][0][0]; 

    for(int i=1;i<=n;i++) dp[i][i][s[i]]=0;
    for(int len=2;len<=n;len++)
    {
        for(int i=1;i+len-1<=n;i++)
        {
            int j=i+len-1;
            for(int m=j;m>i;m-=(k-1))
            {
                for(int S=0;S<=top;S++)
                {
                    if(dp[i][m-1][S]!=_inf)
                    {
                        if(dp[m][j][0]!=_inf) dp[i][j][S<<1]=max(dp[i][j][S<<1],dp[i][m-1][S]+dp[m][j][0]);
                        if(dp[m][j][1]!=_inf) dp[i][j][S<<1|1]=max(dp[i][j][S<<1|1],dp[i][m-1][S]+dp[m][j][1]);
                    }
                }
            }

            if(len-1>=k-1&&(len-1)%(k-1)==0)  //可以缩成一个 
            {
                LL g[2];
                g[0]=g[1]=_inf;

                for(int S=0;S<=top;S++)
                if(dp[i][j][S]!=_inf) g[c[S]]=max(g[c[S]],dp[i][j][S]+w[S]);        
                dp[i][j][0]=g[0]; dp[i][j][1]=g[1]; 
            }
        }
    }
    LL ans=0;
    for(int i=0;i<=top;i++)
    ans=max(ans,dp[1][n][i]);
    printf("%I64d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值