Luogu P4590 [TJOI2018]游园会

Link
求LCS有一个显然的\(O(n^2)\)的dp做法,即设\(f_{i,j}\)表示\(s_i,t_j\)的lcs长度,那么\(f_{i,j}=\max(f_{i-1,j},f_{i,j-1},f_{i-1,j-1}+[s_i=t_j])\)
\(t\)是题目给定的,因此在已知\(f_{i-1},s_i\)的情况下我们是可以求出\(f_i\)的。
因此我们考虑将\(f_i\)当做是一个状态并把它放在自动机上,那么根据转移方程我们就可以的得到自动机上的转移进而求出答案。
同时我们知道\(f_i\)是单调不降的,且相邻两项的差要么为\(0\)要么为\(1\),因此我们可以用\(\Delta f_i\)来表示\(f_i\),这样自动机上的点数就只有\(2^m\)了。
而且LCS的长度就是\(\Delta f_i\)\(1\)的个数,这样我们就可以方便地统计了。
然后设\(f_{i,j,k}\)为长度为\(i\)的字符串,在自动机上\(j\)号点,匹配\(\text{NOI}\)长度为\(k\)时的方案数,预处理出自动机上的转移,然后滚掉\(i\)这一维即可。

#include<cstdio>
#include<cstring>
const int P=1000000007;const char ccf[]="NOI";
int f[2][1<<15|1][3],trans[1<<15|1][3],ans[16];char str[16];
void inc(int&a,int b){a+=b-P,a+=a>>31&P;}
int main()
{
    int n,m,now=0;
    scanf("%d%d%s",&n,&m,str),f[0][0][0]=1;
    for(int i=0;i<1<<m;++i)
	for(int j=0;j<3;++j)
	{
	    int las=-1,to=i;
	    for(int k=m-1;~k;--k) if(i>>k&1) las=k; else if(str[k]==ccf[j]) (~las? to^=1<<las:0),las=k,to^=1<<k;
	    trans[i][j]=to;
	}
    for(int i=1;i<=n;++i,now^=1)
    {
	memset(f[now^1],0,sizeof f[now^1]);
	for(int j=0;j<1<<m;++j) for(int k=0;k<3;++k) if(f[now][j][k]) for(int l=0;l<3;++l) if(k^2||l^2) inc(f[now^1][trans[j][l]][k==l? k+1:!l],f[now][j][k]);
    }
    for(int i=0;i<1<<m;++i) for(int j=0;j<3;++j) inc(ans[__builtin_popcount(i)],f[now][i][j]);
    for(int i=0;i<=m;++i) printf("%d\n",ans[i]);
}
posted @ 2020-06-01 09:53  Shiina_Mashiro  阅读(127)  评论(0编辑  收藏  举报