HNOI2008 GT考试 和 Beijing2017 魔法咒语

GT考试

阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字。他的不吉利数学A1A2...Am(0<=Ai<=9)有M位,不出现是指X1X2...Xn中没有恰好一段等于A1A2...Am. A1和X1可以为0。

分析

很好想的AC自动机上dp,用(f[i][j])表示放了(i)位在节点(j)的方案数。

[f[0][0]=1\ ans=sum_{j=0}^{m-1}f[n][j]\ f[i][j]=sum_{k ightarrow j}f[i-1][k] ]

然后这个式子是(O(10 * N * M))的。

考虑矩阵加速,很像BZOJ4861 [Beijing2017]魔法咒语,于是自己写了写,一遍AC了。

并且我写的还是AC自动机,所以禁忌串完全可以弄多一点。

时间复杂度(O(M^3 log_2 N)),是212603.39807279119026370044348732,随便跑,怪不得这题总时限是1s。

int mod;

int add(int x,int y)
{
	x+=y;
	return x>=mod?x-mod:x;
}

int mul(int x,int y)
{
	return (ll)x*y%mod;
}

co int N=21;
namespace AC
{
	int tot;
	int ch[N][10],fail[N],val[N];
	
	void insert(char s[],int n)
	{
		int u=0;
		for(int i=0;i<n;++i)
		{
			int k=s[i]-'0';
			if(!ch[u][k])
				ch[u][k]=++tot;
			u=ch[u][k];
		}
		val[u]=1;
	}
	
	void getfail()
	{
		std::queue<int>Q;
		for(int i=0;i<10;++i)	
			if(ch[0][i])
				Q.push(ch[0][i]);
		while(Q.size())
		{
			int u=Q.front();Q.pop();
			val[u]|=val[fail[u]];
			for(int i=0;i<10;++i)
			{
				if(ch[u][i])
				{
					fail[ch[u][i]]=ch[fail[u]][i];
					Q.push(ch[u][i]);
				}
				else
					ch[u][i]=ch[fail[u]][i];
			}
		}
	}
	
	int ANS[N][N],A[N][N],c[N][N];
	
	void mul(int a[N][N],int b[N][N])
	{
		for(int k=0;k<=tot;++k)
			for(int i=0;i<=tot;++i)if(a[i][k])
				for(int j=0;j<=tot;++j)if(b[k][j])
					c[i][j]=add(c[i][j],::mul(a[i][k],b[k][j]));
		for(int i=0;i<=tot;++i)
			for(int j=0;j<=tot;++j)
				a[i][j]=c[i][j],c[i][j]=0;
	}
	
	void solve(int n)
	{
		for(int i=0;i<=tot;++i)if(!val[i])
			for(int j=0;j<10;++j)if(!val[ch[i][j]])
				++A[i][ch[i][j]];
		ANS[0][0]=1;
		while(n)
		{
			if(n&1)
				mul(ANS,A);
			mul(A,A);
			n>>=1;
		}
		int ans=0;
		for(int i=0;i<=tot;++i)if(!val[i])
			ans=add(ans,ANS[0][i]);
		printf("%d
",ans);
	}
}
char buf[N];

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int n,m;
	read(n),read(m),read(mod);
	scanf("%s",buf);
	AC::insert(buf,m);
	AC::getfail();
	AC::solve(n);
	return 0;
}

魔法咒语

给定n个原串和m个禁忌串,要求用原串集合能拼出的不含禁忌串且长度为L的串的数量模 1,000,000,007的结果。(60%)n,m<=50,L<=100。(40%)原串长度为1或2,L<=10^18。

ONION_CYC的题解

其实题意的数据范围不太清晰,反正开200个点就足够了。

因为要匹配禁忌串,所以对禁忌串集合建立AC自动机,标记禁忌串结尾节点,以及下传到所有能fail到的点(这些点访问到都相当于匹配了禁忌串)。

(f[i][j])表示匹配到节点(i),长度为(j)的串的数量,先预处理(a[i][j])表示节点 (i) 匹配第 (j) 个原串到达的节点编号,那么就有:

[f [ a[i][j] ] [ L+len[j] ] += f [ i ] [ L ] ]

以上就是60%数据的做法,对于40%的数据使用矩阵快速幂。

假设原串长度均为1,那么DP的转移如下:

[f[i][L]=sum_{j→i}f[j][L−1] ]

这很容易用一个长度为第一维大小(AC自动机节点数)的矩阵维护转移,第L个列向量就是f[i][L]。

如果原串长度有2,那么再记录L-1即可。

答案矩阵如下:

[left[ egin{matrix} f[0][L] & f[0][L-1] & dots & f[i][L] & f[i][L-1] & dots & f[tot][L] & f[tot][L-1] end{matrix} ight] ]

构造转移矩阵,考虑转移矩阵的意义,第(i)行的元素的含义是由(f[i][dots])转移,第(j)列的元素的含义是转移到(f[j][dots])。转移矩阵的两个下标(A[i][j])可以看成从(i)转移到(j)。于是不难构造。

时间复杂度

对于60%的数据,(O(L * ML *N)),是25000000。中间的(ML)是因为禁忌串长似乎与(L)同阶。
对于另40%的数据,(Oleft((2M)^3 log_2 L ight)),是26575424.759098898782962555435915。

关于矩阵的问题

原作者的矩阵写法跟我的是反着的,导致WA了几次。

[(AB)^T=B^TA^T ]

如果将矩阵转置,左右乘也要倒。

另外算快速幂的时候可以边倍增边算答案。

co int maxn=5001,mod=1e9+7;
int n,m,L;
char s[51][101],buf[101]; // 101:possible string length
int len[51];
namespace AC
{
	int tot;
	int ch[maxn][26],val[maxn],fail[maxn],a[maxn][101];
	
	void insert(char s[],int n)
	{
		int u=0;
		for(int i=0;i<n;++i)
		{
			int k=s[i]-'a';
			if(!ch[u][k])
				ch[u][k]=++tot;
			u=ch[u][k];
		}
		val[u]=1;
	}
	
	void build()
	{
		std::queue<int>Q;
		for(int i=0;i<26;++i)
			if(ch[0][i])
				Q.push(ch[0][i]);
		while(Q.size())
		{
			int u=Q.front();Q.pop();
			for(int i=0;i<26;++i)
			{
				if(ch[u][i])
				{
					fail[ch[u][i]]=ch[fail[u]][i];
					Q.push(ch[u][i]);
					val[ch[u][i]]|=val[fail[ch[u][i]]];
				}
				else
					ch[u][i]=ch[fail[u]][i];
			}
		}
		memset(a,-1,sizeof a);
		for(int k=1;k<=n;++k)
			for(int i=0;i<=tot;++i)
			{
				int u=i;
				for(int j=0;j<len[k];++j)
				{
					if(!val[u])
						u=ch[u][s[k][j]-'a'];
					else break;
				}
				if(!val[u])
					a[i][k]=u;
			}
	}
}

int add(int x,int y)
{
	x+=y;
	return x>=mod?x-mod:x;
}

int mul(int x,int y)
{
	return (ll)x*y%mod;
}

namespace T1
{
	using namespace AC;
	int f[maxn][101];
	
	void solve()
	{
		f[0][0]=1;
		for(int l=0;l<L;++l)
			for(int i=0;i<=tot;++i)if(f[i][l])
				for(int j=1;j<=n;++j)if(~a[i][j]&&l+len[j]<=L)
					f[a[i][j]][l+len[j]]=add(f[a[i][j]][l+len[j]],f[i][l]);
		int ans=0;
		for(int i=0;i<=tot;++i)
			if(f[i][L]&&!val[i])
				ans=add(ans,f[i][L]);
		printf("%d
",ans);
	}
}

namespace T2
{
	using namespace AC;
	co int maxn=101;
	int N,A[maxn*2][maxn*2],ANS[maxn*2][maxn*2],c[maxn*2][maxn*2];
	
	void mul(int a[maxn*2][maxn*2],int b[maxn*2][maxn*2])
	{
		for(int k=0;k<=N;++k)
			for(int i=0;i<=N;++i)if(a[i][k])
				for(int j=0;j<=N;++j)if(b[k][j])
					c[i][j]=add(c[i][j],::mul(a[i][k],b[k][j]));
		for(int i=0;i<=N;++i)
			for(int j=0;j<=N;++j)
				a[i][j]=c[i][j],c[i][j]=0;
    }
	
	void solve()
	{
		N=tot*2+1;
		for(int i=0;i<=tot;++i)
		{
			for(int j=1;j<=n;++j)if(~a[i][j])
			{
				if(len[j]==1)
					++A[i*2][a[i][j]*2];
				else
					++A[i*2+1][a[i][j]*2];
			}
			A[i*2][i*2+1]=1;
		}
		ANS[0][0]=1;
		while(L)
		{
			if(L&1)
				mul(ANS,A);
			mul(A,A);
			L>>=1;
		}
		int ans=0;
		for(int i=0;i<=tot;++i)if(!val[i])
			ans=add(ans,ANS[0][i*2]);
		printf("%d
",ans);
	}
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	read(n),read(m),read(L);
	for(int i=1;i<=n;++i)
	{
		scanf("%s",s[i]);
		len[i]=strlen(s[i]);
	}
	for(int i=1;i<=m;++i)
	{
		scanf("%s",buf);
		AC::insert(buf,strlen(buf));
	}
	AC::build();
	if(L<=100)
		T1::solve();
	else
		T2::solve();
	return 0;
}
原文地址:https://www.cnblogs.com/autoint/p/10322870.html