【AC自动机】【矩阵加速】BZOJ4861魔法咒语

分析:

巨恶心的一题双代码题。

对于前半部分,把禁止出现的字符串建一颗AC自动机。

然后枚举每个位置用了某个模板串后转移到哪里。

然后直接DP即可。

但是对于后半部分数据,则必须写一个矩阵加速。。。
因为模板串长度不超过2,只需要对所有长度为2的建一个虚拟节点就行了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define SF scanf
#define PF printf
#define MAXN 310
#define MOD 1000000007
using namespace std;
typedef long long ll;
char s[MAXN][MAXN];
char s1[MAXN][MAXN];
int n,m,l,tot;
ll c[MAXN][MAXN];
int maxS;
struct matrix{
	ll a[MAXN][MAXN];
	void operator *=(const matrix &x) {
		memset(c,0,sizeof c);
		for(int i=0;i<maxS;i++)
			for(int j=0;j<maxS;j++)
				for(int k=0;k<maxS;k++)	
					c[i][j]=(c[i][j]+a[i][k]*x.a[k][j]%MOD)%MOD;
		for(int i=0;i<maxS;i++)
			for(int j=0;j<maxS;j++)
				a[i][j]=c[i][j];
	}
}T,e,res;
struct node{
	int flag;
	node *fail;
	node *ch[26];	
}Tree[MAXN];
node *ncnt=Tree,*rt=Tree;
int tr[MAXN][MAXN];
int len[MAXN];
void ins(node *now,char *c){
	if(*c==0){
		now->flag=1;
		return ;	
	}
	int d=*c-'a';
	if(now->ch[d]==NULL)
		now->ch[d]=++ncnt;
	ins(now->ch[d],++c);
}
queue<node*> q;
void build(){
	q.push(rt);
	rt->fail=rt;
	while(!q.empty()){
		node *now=q.front();
		q.pop();
		for(int i=0;i<26;i++){
			node *x=now;
			if(x->ch[i]==NULL){
				if(x==rt)
					x->ch[i]=rt;
				else
					x->ch[i]=x->fail->ch[i];
				continue;
			}
			q.push(x->ch[i]);
			if(now==rt){
				x->ch[i]->fail=rt;
				continue;
			}
			x=x->fail;
			while(x!=rt&&x->ch[i]==NULL)
				x=x->fail;
			if(x->ch[i]!=NULL)
				now->ch[i]->fail=x->ch[i];
			else
				now->ch[i]->fail=rt;
		}
		now->flag|=now->fail->flag;
	}
}
matrix fsp(matrix &x,int t){
	for(int i=0;i<tot*2;i++)
		e.a[i][i]=1;
	while(t){
		if(t&1)
			e*=x;
		x*=x;
		t>>=1;
	}
	return e;
}
int dp[MAXN][MAXN];
int main(){
	SF("%d%d%d",&m,&n,&l);
	for(int i=1;i<=m;i++){
		SF("%s",s1[i]);
		len[i]=strlen(s1[i]);
	}
	for(int i=1;i<=n;i++){
		SF("%s",s[i]);
		ins(rt,s[i]);
	}
	build();
	tot=ncnt-rt+1;
	for(int i=0;i<tot;i++)
		for(int j=1;j<=m;j++){
			node *x=rt+i;
			if(x->flag)
				tr[i][j]=-1;
			else{
				for(int k=0;k<len[j]&&x->flag==0;k++)
					x=x->ch[s1[j][k]-'a'];
				if(x->flag)
					tr[i][j]=-1;
				else
					tr[i][j]=x-rt;
			}
		}
	if(l<=100){
		dp[0][0]=1;
		for(int i=0;i<l;i++)
			for(int now=0;now<tot;now++)
				for(int j=1;j<=m;j++)
					if(tr[now][j]!=-1&&i+len[j]<=l)
						(dp[i+len[j]][tr[now][j]]+=dp[i][now])%=MOD;
		int ans=0;
		for(int now=0;now<tot;now++)
			ans=(ans+dp[l][now])%MOD;
		PF("%d",ans);
	}
	else{
		maxS=tot*2;
		for(int i=0;i<tot;i++)
			for(int j=1;j<=m;j++){
				if(tr[i][j]==-1)
					continue;
				if(len[j]==1)
					T.a[i+tot][tr[i][j]+tot]++;
				else
					T.a[i][tr[i][j]+tot]++;
			}
		for(int i=0;i<tot;i++)
			T.a[i+tot][i]++;
		res.a[0][tot]=1;
		res*=fsp(T,l);
		int ans=0;
		for(int i=tot;i<=2*tot;i++)
			ans=(ans+res.a[0][i])%MOD;
		PF("%d\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值