BZOJ传送门

洛谷传送门


解析:

为什么endend又是关键字啊啊啊啊!!!

思路:

不要试图用容斥原理来做这道题。。。

这就是一个ACAC自动机上的DPDP

首先建出ACAC自动机,这时候我们得到所有串之间的包含关系。

然后,如果您有直接统计答案的做法,请私信博主,方便完善题解,这里提供一种反向统计的方法。

我们不统计满足条件的方案数,我们统计不满足的方案数。

因为总方案十分好算,就是26m26^m,所以满足条件的方案数就是不满足的补集。

那么考虑如下DPDPdp[len][k]+=dp[len1][j]dp[len][k]+=dp[len-1][j]
其中lenlen是当前考虑的前缀字符串的长度,jj是所有能够转移到kk的自动机结点集合。

这个DPDP十分显然,我们现在考虑怎么处理不合法的方案。
考虑我们不能让任何一个ACAC自动机中已经插入的字符串出现在方案中,我们只需要记录结点是否是某个字符串的结尾。每次DPDP之前跳failfail链检验一下就行了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

#define end End
cs int mod=10007;
cs int N=6005;

int son[N][26],dp[104][N],fail[N],end[N];
int tot=1,n,m;

inline void insert(char c[]){
	int len=strlen(c),now=1;
	for(int re i=0;i<len;++i){
		int x=c[i]-'A';
		if(!son[now][x])son[now][x]=++tot;
		now=son[now][x];
	}
	end[now]=true;
}

inline void build_ACauto(){
	queue<int> q;
	q.push(1);
	while(!q.empty()){
		int now=q.front();
		q.pop();
		for(int re i=0;i<26;++i){
			if(son[now][i]){
				int tmp=fail[now];
				while(!son[tmp][i])tmp=fail[tmp];
				fail[son[now][i]]=son[tmp][i];
				q.push(son[now][i]);
			}
		}
	}
}

inline int quickpow(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

char c[101];
signed main(){
	for(int re i=0;i<26;++i)son[0][i]=1;
	cin>>n>>m;
	for(int re i=1;i<=n;++i){
		scanf("%s",c);
		insert(c);
	}
	build_ACauto();
	dp[0][1]=1;
	for(int re k=0;k<m;++k){
		for(int re j=1;j<=tot;++j){
			for(int re i=0;i<26;++i){
				int now=j;
				bool flag=true;
				while(now){
					if(end[son[now][i]]){
						flag=false;
						break;
					}
					now=fail[now];
				}
				if(!flag)continue;
				now=j;
				while(!son[now][i])now=fail[now];
				now=son[now][i];
				dp[k+1][now]+=dp[k][j];
				dp[k+1][now]%=mod;
			}
		}
	}
	int ans=0;
	for(int re i=1;i<=tot;++i)ans=(ans+dp[m][i])%mod;
	
	cout<<(quickpow(26,m)-ans+mod)%mod;
	return 0;
}
posted on 2018-10-10 10:15  zxyoi_dreamer  阅读(75)  评论(0编辑  收藏