其他分享
首页 > 其他分享> > [TJOI2018] 游园会

[TJOI2018] 游园会

作者:互联网

一、题目

点此看题

二、解法

考虑字符串计数 \(dp\) 的常见模型,设 \(dp(i,...,k)\) 表示已经填入了 \(i\) 个字符,现在串已经匹配到了 \(\tt NOI\) 长度为 \(k\) 的前缀,那么我们还需要把最长公共子序列记录到状态里面。

考虑最长公共子序列的求法是普通 \(dp\),设 \(f(i,j)\) 表示考虑 \(A\) 串长度为 \(i\) 的前缀和 \(B\) 串长度为 \(j\) 的前缀,它们的最长公共子序列长度,这里我们让 \(A\) 串是我们正在处理的串:

\[f(i,j)=\max\{f(i-1,j),f(i,j-1),f(i-1,j-1)+(A_i=B_j)\} \]

考虑用一个自动机来表示这个 \(f\),自动机上的每一个节点都对应一个 \(f\),然后我们把原来的状态记成 \(dp(i,j,k)\) 表示匹配到了自动机上的第 \(j\) 个节点,这和 \(\tt AC\) 自动机、后缀自动机上 \(dp\) 类似,只不过自己设计自动机要更加灵活。

可以考虑记 \(f\) 的差分数组,这样自动机上的节点数一共只有 \(2^{15}\) 种,外层 \(dp\) 转移枚举填入 NOI 的哪一种字符,内层 \(dp\) 转移直接由上一层推出下一层即可,时间复杂度 \(O(nk\cdot 2^k)\)

这种用另一个 \(dp\) 检验合法,设计自动机记录状态的方法,我们称之为 \(dp\) 套 \(dp\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 20;
const int N = 32780;
const int MOD = 1e9+7;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],b[M],ans[M],siz[N],dp[2][N][3];char s[M];
int encode(int *a)
{
	int r=0;
	for(int i=0;i<m;i++)
		r|=(a[i+1]-a[i])<<i;
	return r;
}
void decode(int *a,int r)
{
	for(int i=0;i<m;i++) a[i+1]=(r>>i)&1;
	for(int i=1;i<=m;i++) a[i]+=a[i-1];
}
void trans(int w,int r,int p,char c,int v)
{
	decode(a,r);
	for(int i=1;i<=m;i++)
		b[i]=max(max(a[i],b[i-1]),a[i-1]+(c==s[i]));
	int tr=encode(b);
	dp[w][tr][p]=(dp[w][tr][p]+v)%MOD;
}
signed main()
{
	n=read();m=read();
	scanf("%s",s+1);dp[0][0][0]=1;
	for(int i=1;i<(1<<15);i++) siz[i]=siz[i>>1]+(i&1);
	for(int i=0;i<n;i++)
	{
		int w=(i&1),tw=w^1;
		for(int j=0;j<(1<<m);j++)
			for(int p=0;p<3;p++)
				dp[tw][j][p]=0;
		for(int j=0;j<(1<<m);j++)
		{
			if(dp[w][j][0])
			{
				trans(tw,j,1,'N',dp[w][j][0]);
				trans(tw,j,0,'O',dp[w][j][0]);
				trans(tw,j,0,'I',dp[w][j][0]);
			}
			if(dp[w][j][1])
			{
				trans(tw,j,1,'N',dp[w][j][1]);
				trans(tw,j,2,'O',dp[w][j][1]);
				trans(tw,j,0,'I',dp[w][j][1]);
			}
			if(dp[w][j][2])
			{
				trans(tw,j,1,'N',dp[w][j][2]);
				trans(tw,j,0,'O',dp[w][j][2]);
			}
		}
	}
	for(int i=0;i<(1<<m);i++)
		for(int p=0;p<3;p++)
			ans[siz[i]]=(ans[siz[i]]+dp[n&1][i][p])%MOD;
	for(int i=0;i<=m;i++)
		printf("%d\n",ans[i]);
}

标签:TJOI2018,const,前缀,int,游园会,长度,自动机,dp
来源: https://www.cnblogs.com/C202044zxy/p/15790173.html