【BZOJ】4861: [Beijing2017]魔法咒语-AC自动机+DP+矩乘

传送门:bzoj4861


题解

首先把忌讳词建成AC自动机,所有串的 e n d end end结点和可以通过跳 f a i l fail fail链到一个 e n d end end结点的点都不能走到( g e t f a i l getfail getfail时预处理即可)。

L ≤ 100 L\leq 100 L100时,考虑在AC自动机上DP:
DP算是比较套路的了。
f [ i ] [ j ] f[i][j] f[i][j]表示已经选择了 i i i个字符,走到了自动机上 j j j结点的方案数,有
f [ j + s z k ] [ t r a n s [ j ] [ k ] ] + = f [ i ] [ j ] f[j+sz_k][trans[j][k]]+=f[i][j] f[j+szk][trans[j][k]]+=f[i][j]
其中 k k k枚举的是基本词汇, s z k sz_k szk是串长, t r a n s [ j ] [ k ] trans[j][k] trans[j][k]表示在 j j j结点时再接上串 k k k后会走到的结点。
设初始值 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1 O ( n 3 ) D P O(n^3)DP O(n3)DP即可。

L > 100 L>100 L>100时, D P DP DP的复杂度显然不可接受,但考虑到 l e n ≤ 2 len\leq 2 len2
也就是一个最多关乎 i − 1 , i − 2 i-1,i-2 i1,i2两项的线性递推式,直接矩阵快速幂即可。
复杂度 O ( n 3 l o g L ) O(n^3logL) O(n3logL)


代码

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7,N=110;

int n,m,L,ans,cnt,trans[N][N];
int sz[N],len,mx,ed[N];
char s[N][N],t[N];

inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}

struct AC{
    queue<int>Q;
	int ch[N][26],f[N];
	
	inline void ins(char *s)
	{
		int i,alp,u=0;len=strlen(s);
		for(i=0;i<len;++i){
			alp=s[i]-'a';
			if(!ch[u][alp]) ch[u][alp]=++cnt;
			u=ch[u][alp];
		}
		ed[u]=1;
	}
	
	void getfail()
	{
		int i,x,y,z,u;
		for(i=0;i<26;++i){
			x=ch[0][i];if(x) Q.push(x);
		}
		for(;!Q.empty();){
			x=Q.front();Q.pop();y=f[x];
			for(i=0;i<26;++i){
				z=ch[x][i];u=ch[y][i];
				if(!z) {ch[x][i]=u;continue;}
				f[z]=u;ed[z]|=ed[u];Q.push(z);
			}
		}
	}
	
	inline void getrans(int id,char *s)
	{
		int i,j,u;len=strlen(s);
		mx=max(mx,len);sz[id]=len;
		for(i=0;i<len;++i) s[i]-='a';
		for(i=0;i<=cnt;++i) if(!ed[i]){
			u=i;
			for(j=0;j<len;++j){
			  u=ch[u][(int)s[j]];
			  if(ed[u]) {u=-1;break;}	
			}
			trans[i][id]=u;
		}
	}
}ac;

namespace pA{
    int dp[N][N];
    
	void solve(){
		int i,j,k,res;
		dp[0][0]=1;
	    for(i=0;i<L;++i)
	      for(j=0;j<=cnt;++j)
	       if(dp[i][j]){
	  	       res=dp[i][j];
	  	       for(k=1;k<=n;++k) 
				 if((~trans[j][k]) && i+sz[k]<=L)
	  	 	       ad(dp[i+sz[k]][trans[j][k]],res);
	       }
	    for(i=0;i<=cnt;++i) ad(ans,dp[L][i]);
	    printf("%d",ans);
	}
}

namespace pB{
	int lim;
	
	struct Mat{
		int v[N<<1][N<<1];
		inline void itia(){for(int i=0;i<=lim;++i) v[i][i]=1;}
	}g,e,re;
	
	inline void Mul(Mat &x,Mat y)
	{
		int i,j,k,res;
		for(i=0;i<=lim;++i)
		 for(j=0;j<=lim;++j){
		 	res=0;
		 	for(k=0;k<=lim;++k) 
			  ad(res,1ll*x.v[i][k]*y.v[k][j]%mod);
			re.v[i][j]=res;
		 }
		for(i=0;i<=lim;++i)
		 for(j=0;j<=lim;++j)
		  x.v[i][j]=re.v[i][j];
	}
	
	void maker()
	{
		int i,j;
		if(mx==1){
		   lim=cnt;
		   for(i=0;i<=cnt;++i) 
		    if(!ed[i])
		      for(j=1;j<=n;++j) 
			    if(~trans[i][j])
		   	     g.v[i][trans[i][j]]++;
		}else{
		   lim=(cnt<<1)+1;
		   for(i=0;i<=cnt;++i)
		    for(j=1;j<=n;++j)
		     if(~trans[i][j]){
		     	if(sz[j]==1)
		     	 g.v[i][trans[i][j]]++;
		     	else
		     	 g.v[i+cnt+1][trans[i][j]]++;
		     }
		   for(i=0;i<=cnt;++i) g.v[i][i+cnt+1]++;
		}
	}
	
	void solve()
	{
		int i,j;
		maker();e.itia();
		for(;L;L>>=1,Mul(g,g))
		 if(L&1) Mul(e,g);
		for(i=0;i<=cnt;++i){
			ad(ans,e.v[0][i]);
		}
		printf("%d",ans);
	}
}

int main(){
	int i;scanf("%d%d%d",&n,&m,&L);
	for(i=1;i<=n;++i) scanf("%s",s[i]);
	for(i=1;i<=m;++i){scanf("%s",t);ac.ins(t);}
	ac.getfail();memset(trans,0xff,sizeof(trans));
	for(i=1;i<=n;++i) ac.getrans(i,s[i]);
	if(L<=100) pA::solve();
	else pB::solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值