208.10.10【JSOI2007】【BZOJ1030】【洛谷P4052】文本生成器(AC自动机)(DP)

BZOJ传送门

洛谷传送门


解析:

为什么 e n d end end又是关键字啊啊啊啊!!!

思路:

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

这就是一个 A C AC AC自动机上的 D P DP DP

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

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

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

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

那么考虑如下 D P DP DP d p [ l e n ] [ k ] + = d p [ l e n − 1 ] [ j ] dp[len][k]+=dp[len-1][j] dp[len][k]+=dp[len1][j]
其中 l e n len len是当前考虑的前缀字符串的长度, j j j是所有能够转移到 k k k的自动机结点集合。

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


代码:

#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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值