洛谷P4590 [TJOI2018]游园会

题目描述

题解

首先我们考虑如果我们知道了原串,那我们要求两个串的 $\text{Lcs}$ 的长度就是 $\text{dp}$ : $f[i][j]$ 表示 $S$ 串到 $i$ , $T$ 串到 $j$ 的 $\text{Lcs}$ 长度,考虑转移: $f[i][j]=max(f[i-1][j],f[i][j-1],f[i-1][j-1]+[S[i]==T[j]])$ ,我们可以发现 $f[i][j]-f[i][j-1]\in \{0,1\}$ 。回到原题,我们可以做 $\text{dp}$ : $F[i][S][0/1/2]$ 表示已经放完了前 $i$ 个位置,如果把前面的位置和匹配串拿去做 $f$ 的 $\text{dp}$ 的话, $f[i]$ 的差分数组组成的状态为 $S$ ,最后 $0/1/2$ 个字母是和 $NOI$ 的前几个字母是一样的的方案数。在此之前我们可以预处理出 $g[S][c]$ 表示目前差分数组的状态为 $S$ ,放入 $c$ 后新的差分数组的状态。考虑转移的话就枚举下一个位置放什么字母即可。效率: $O(n2^k)$ 。

考虑从假设我们已经知道了题目中的某个信息,如何求答案入手,可能可以得到正解。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1<<15,P=1e9+7;
int n,k,m,g[N][3],a[20],b[20],X,Y=1,t[N],s[20],f[2][N][3];
char h[20],d[5]="NOI";
int get(int s,int c){
    for (int i=1;i<=k;i++)
        a[i]=a[i-1]+(s&1),s>>=1;
    for (int i=1;i<=k;i++)
        b[i]=max(max(b[i-1],a[i]),
            a[i-1]+(d[c]==h[i]));
    for (int i=k;i;i--)
        s<<=1,s|=(b[i]-b[i-1]);
    return s;
}
int main(){
    scanf("%d%d%s",&n,&k,h+1);
    m=1<<k;f[0][0][0]=1;
    for (int i=0;i<m;i++)
        for (int c=0;c<3;c++)
            g[i][c]=get(i,c);
    for (int i=1;i<=n;i++){
        memset(f[Y],0,sizeof f[Y]);
        for (int j=0;j<m;j++)
            for (int k=0,o;k<3;k++)
                for (int c=0;c<3;c++){
                    if (!c) o=1;
                    else if (c<2) o=k==1?2:0;
                    else o=k==2?3:0;
                    if (o==3) continue;
                    (f[Y][g[j][c]][o]+=f[X][j][k])%=P;
                }
        X^=1,Y^=1;
    }
    for (int i=0;i<m;i++){
        t[i]=t[i>>1]+(i&1);
        for (int j=0;j<3;j++)
            (s[t[i]]+=f[X][i][j])%=P;
    }
    for (int i=0;i<=k;i++) printf("%d\n",s[i]);
    return 0;
}

 

posted @ 2020-02-20 22:01  xjqxjq  阅读(147)  评论(0编辑  收藏  举报