#2575. 「TJOI2018」party DP

题解:

据说这样的套路叫DP套DP?之前也做过一道最长上升子序列,大概思路都是在DP中表示另外一个DP数组的状态,比如这道题就是利用 f[i][j] f [ i ] [ j ] f[i][j1] f [ i ] [ j − 1 ] 最多只相差 1 1 (f[i][j]表示兑奖串前 i i 位与奖章串前j位的最长公共子序列),利用差分+状态压缩表示出某一 i i 的所有f[i][j],然后预处理每个状态在填了某个字母后的转移,直接转移即可。对于不能出现 NOI N O I 的限制,用一维来表示最后几个字母的状态就行了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
//NOI
//012
const int Maxn=1010;
const int Maxk=20;
const int mod=1000000007;
void upd(int &x,int y){x+=y;if(x>=mod)x-=mod;}
char s[Maxk];
int N,K,a[Maxk],to[32770][3],f[2][3][32770];//0 1:N 2:NO
int get(char c)
{
    if(c=='N')return 0;
    if(c=='O')return 1;
    return 2;
}
int t1[Maxk],t2[Maxk];
void work(int S)
{
    for(int i=0;i<3;i++)
    {
        t1[0]=t2[0]=0;
        for(int j=0;j<K;j++)t1[j+1]=t1[j]+((S&(1<<j))?1:0);
        for(int j=1;j<=K;j++)
        {
            t2[j]=max(t1[j],t2[j-1]);
            if(a[j]==i)t2[j]=max(t2[j],t1[j-1]+1);
        }
        int tmp=0;
        for(int j=1;j<=K;j++)if(t2[j]>t2[j-1])tmp|=(1<<(j-1));
        to[S][i]=tmp;
    }
}
int one(int x)
{
    int re=0;
    while(x)
    {
        re+=(x&1);
        x>>=1;
    }
    return re;
}
int ans[Maxk];
int main()
{
    scanf("%d%d%s",&N,&K,s+1);
    for(int i=1;i<=K;i++)a[i]=get(s[i]);
    for(int i=0;i<(1<<K);i++)work(i);
    int now=0;
    memset(f[now],0,sizeof(f[now]));
    f[0][0][0]=1;
    for(int i=0;i<N;i++)
    {
        now^=1;
        memset(f[now],0,sizeof(f[now]));
        for(int S=0;S<(1<<K);S++)
        {
            int t;
            t=f[now^1][0][S];//
            upd(f[now][1][to[S][0]],t);//N
            upd(f[now][0][to[S][1]],t);//O
            upd(f[now][0][to[S][2]],t);//I

            t=f[now^1][1][S];//N
            upd(f[now][1][to[S][0]],t);//N
            upd(f[now][2][to[S][1]],t);//O
            upd(f[now][0][to[S][2]],t);//I

            t=f[now^1][2][S];//NO
            upd(f[now][1][to[S][0]],t);//N
            upd(f[now][0][to[S][1]],t);//O
        }
    }
    for(int i=0;i<3;i++)
    for(int j=0;j<(1<<K);j++)
    upd(ans[one(j)],f[now][i][j]);
    for(int i=0;i<=K;i++)printf("%d\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值