bzoj 4565 字符合并

Written with StackEdit.

Description

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

Input

第一行两个整数\(n,k\)。接下来一行长度为n的01串,表示初始串。接下来\(2^k\)行,每行一个字符\(c_i\)和一个整数\(w_i\)\(c_i\)表示长度为\(k\)\(01\)串连成二进制后按从小到大顺序得到的第i种合并方案得到的新字符,\(w_i\)表示对应的第\(i\)种方案对应获得的分数。

Output

输出一个整数表示答案.

Sample Input

3 2
101
1 10
1 10
0 20
1 30

Sample Output

40

  • 第3行到第6行表示长度为2的4种01串合并方案。00->1,得10分,01->1得10分,10->0得20分,11->1得30分。

Hint

\(1\leq n\leq 300,0\leq ci\leq 1,1\leq w_i,k\leq8.\)

Solution

并不会做...于是临摹zxTLEdalao的题解...然后自己似乎懂了一点.

  • 这道题\(k\leq8\),做出一副状压的嘴脸虽然的确也用到了状压,但解决本题的核心在于区间\(dp\).
  • 考虑令\(f[l][r][p]\)表示将原串\([l,r]\)合并为\(t\)能获得的最大收益.
  • 显然是满足无后效性的.合并起来之后就不再关心合并过程了.
  • 枚举中间的端点\(mid\),使得\(mid\)右边的子串合并起来是\(t\)的最后一位数字,左边的子串合并起来是\(t\)前面的数字.
  • 注意到,合并时只能连续的\(k\)个数字合并,那么可以合并成\(1\)位数字的子串长度一定为\(1,k,2k-1......\),枚举\(mid\)时相差为\(k-1\)即可.
  • 特殊情况:从\(l\)\(r\)恰好有\(k\)个数,这时我们直接合并,用辅助数组\(g\)求出此时的\(f\),
#include<bits/stdc++.h>
#define inf (1LL<<60)//防止相加爆long long 
using namespace std;
typedef long long ll;
inline ll read()
{
    int out=0,fh=1;
    char jp=getchar();
    while ((jp>'9'||jp<'0')&&jp!='-')
        jp=getchar();
    if (jp=='-')
        {
            fh=-1;
            jp=getchar();
        }
    while (jp>='0'&&jp<='9')
        {
            out=out*10+jp-'0';
            jp=getchar();
        }
    return out*fh;
}
inline void upd(ll &x,ll y)
{
    x=max(x,y);
}
const int MAXN=329;
const int MAXS=(1<<8)+10;
int a[MAXN];
int n,k,lim;
char buf[MAXN];
ll w[MAXS],c[MAXS]; 
ll f[MAXN][MAXN][MAXS];//f[l][r][k]:将原串的[l,r]合并为t能获得的最大分数 
int main()
{
    n=read(),k=read();
    scanf("%s",buf+1);
    for(int i=1;i<=n;++i)
        a[i]=(buf[i]=='1');
    lim=(1<<k)-1;
    for(int i=0;i<=lim;++i)
        c[i]=read(),w[i]=read();
    ll ans=0;
    for(int i=1;i<=n;++i)   
        for(int j=1;j<=n;++j)
            for(int p=0;p<=lim;++p)
                f[i][j][p]=-inf;
    for(int i=n;i>=1;--i)
        for(int j=i;j<=n;++j)
            {
                if(i==j)
                    {
                        f[i][j][a[i]]=0;
                        continue;
                    }
                int len=j-i;
                len%=k-1;
                if(!len)
                    len=k-1;
                for(int mid=j;mid>i;mid-=k-1)
                    for(int op=0;op<(1<<len);++op)
                        {
                            upd(f[i][j][op<<1],f[i][mid-1][op]+f[mid][j][0]);
                            upd(f[i][j][op<<1|1],f[i][mid-1][op]+f[mid][j][1]);
                        }
                if(len==k-1)
                    {
                        ll g[2];
                        g[0]=g[1]=-inf;
                        for(ll op=0;op<(1<<k);++op)
                            upd(g[c[op]],f[i][j][op]+w[op]);
                        f[i][j][0]=g[0];
                        f[i][j][1]=g[1];
                    }
            }
    for(int i=0;i<=lim;++i)
        upd(ans,f[1][n][i]);
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/jklover/p/9997386.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值