Luogu4590 [TJOI2018]游园会

Luogu4590 [TJOI2018]游园会

\(DP\)\(DP\)

\(dp_{i,j,k}\)表示选到第\(i\)个字符,匹配状态为\(j\)\(NOI\)子串已经匹配了\(k\)个的方案数。

问题在于\(j\)无法用字符串知识解决。

由于\(k\)很小,我们可以考虑\(dp\)来求状态。

最长公共子序列的\(dp\)方程为:

\[ dp_{i,j}=\max \{ dp_{i-1,j} , dp_{i,j-1} , dp_{i-1,j-1}+1\} \]

我们已经知道了模式串,我们先用一个数组来表示状态。

\(a_i\)表示当前状态下,在模式串前\(i\)个位置匹配的最长长度。

现在我们加入一个字符\(c\),我们需要把\(a\)更新为\(b\)

\[b_i=\max \{ a_i , b_{i-1} , a_{i-1} + [c=s_i]\} \]

考虑\(dp\)方程的含义,\(a_i\)表示不考虑加入新字符后原来的值,\(b_{i-1}\)表示不用当前字符在第\(i\)位匹配,利用前\(i-1\)位得到的新\(dp\)值,\(a_{i-1} + [c=s_i]\)即在当前位匹配。

现在我们就需要把\(b\)数组压缩成一个状态,那么\(15\)个数,每个位置取值\([0,15]\),总状态数为……

\[16^{15}=2^{60} \]

出大问题!

显然有许多多余状态,利用最长公共子序列的性质\(b_i-b_{i-1} \in \{ 0,1\}\),即最多可能多匹配一位,差分以后压缩成二进制数,状态数降为\(2^k\)

每次\(dp\)时解压状态,然后压缩回去即可。

一开始模数写成\(998244353\),直接挂成\(10pts\)

\(Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int p=1000000007;
int n,k,cur,pre;
int dp[2][1 << 15][3];
int a[25],b[25],ans[25],ct[1 << 15];
char s[25];
void add(int &x,int y)
{
    x=(x+y)%p;
}
void hash_back(int x)
{
    a[0]=0;
    for (int i=1;i<=k;++i)
        a[i]=a[i-1]+((x >> i-1) & 1);
}
int get_hash()
{
    int s=0;
    for (int i=1;i<=k;++i)
        s+=(1 << i-1)*(b[i]-b[i-1]);
    return s;
}
void Dp(int val,char c,int fr,int opt)
{
    hash_back(fr);
    b[0]=0;
    for (int i=1;i<=k;++i)
        b[i]=max(max(a[i],b[i-1]),a[i-1]+(c==s[i]));
    int t=get_hash();
    add(dp[cur][t][opt],val);
}
int main()
{
    scanf("%d%d",&n,&k);
    scanf("%s",s+1);
    cur=0,pre;
    dp[cur][0][0]=1;
    for (int i=1;i<=n;++i)
    {
        pre=cur,cur^=1;
        memset(dp[cur],0,sizeof(dp[cur]));
        for (int j=0;j<(1 << k);++j)
        {
            if (dp[pre][j][0])
                Dp(dp[pre][j][0],'N',j,1),Dp(dp[pre][j][0],'O',j,0),Dp(dp[pre][j][0],'I',j,0);
            if (dp[pre][j][1])
                Dp(dp[pre][j][1],'N',j,1),Dp(dp[pre][j][1],'O',j,2),Dp(dp[pre][j][1],'I',j,0);
            if (dp[pre][j][2])
                Dp(dp[pre][j][2],'N',j,1),Dp(dp[pre][j][2],'O',j,0);
        }
    }
    for (int i=1;i<(1 << k);++i)
        ct[i]=ct[i >> 1]+(i & 1);
    for (int i=0;i<(1 << k);++i)
        for (int j=0;j<3;++j)
            add(ans[ct[i]],dp[cur][i][j]);
    for (int i=0;i<=k;++i)
        printf("%d\n",ans[i]);
    return 0;
}
posted @ 2020-11-27 15:21  GK0328  阅读(45)  评论(0编辑  收藏  举报