题目
给你一个m,一个n,
m个要被禁止的模式串,
问长度恰为n的串里,
不包含任意一个模式串的方案数是多少
方案数模10W
思路来源
https://blog.youkuaiyun.com/morgan_xww/article/details/7834801(思路,强烈推荐,博主好文)
https://blog.youkuaiyun.com/u013446688/article/details/47378255(代码)
题解
把AC自动机上的每个节点的编号看作是一个状态,
是一种独一无二的状态,而状态i->状态j转移,
即取决于中间的边A,T,C,G,
这样边合法当且仅当两个节点合法
①一个模式串的尾一定不合法,
那么fail是一个模式串的尾的地方也不合法
比如ACGT,CG,第二个CG在G处不合法,则第一个G也不合法
②每个字符都可以像A,T,C,G转移
而可能在自动机里A无法向A转移,因为A的后继A不存在,
这时我们就把该节点的后继,赋值为该节点fail的后继即可,
由于最坏情况也不过是该节点的fail==root,
从而在不添加新节点的情况下把后继赋成自己,
不会造成死循环,所以是可行的
③如果ac自动机里最后cnt个节点,那矩阵M的规模是cnt*cnt的
矩阵M[i][j]记录的是长度为1的串中状态i->状态j有M[i][j]种转移方案(注意状态是点,两个点夹一条边)
其n次幂即为长度为n的串的方案数,矩阵快速幂一波即可
心得
本来矩阵快速幂写了个递归的,结果爆栈了返回RE,
还以为数组开小了,或是下标写错了
一题苟快2h才调出来,还是循环大法好啊
愿长记此题,不辱使命
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
const int mod=100000;
struct node
{
int fail,Next[4];
bool tag;
}trie[105];
char s[105];
int que[105];//建ac自动机的队列
int root,tot;//根的编号 节点编号
map<char,int>id;
ll m,n,ans;
struct Matrix
{
ll a[110][110];
int r;
Matrix():r(105){
memset(a,0,sizeof a);
}
Matrix(int n):r(n)//考虑0次的单位矩阵
{
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
a[i][j]=0;
}
}
}
Matrix operator*(const Matrix &b)const
{
Matrix tmp(r);
for(int i=0;i<r;++i)
{
for(int j=0;j<r;++j)
{
for(int k=0;k<r;++k)
{
tmp.a[i][j]+=(a[i][k]*b.a[k][j])%mod;
if(tmp.a[i][j]>=mod)tmp.a[i][j]%=mod;
}
}
}
return tmp;
}
}M;
Matrix modpow(Matrix x,ll n,ll mod)
{
/*
if(n==1)return x;
Matrix p(x.r);
p=modpow(x,n/2,mod);
Matrix res(x.r);
res=p*p;
if(n&1)res=res*x;
return res;*/
Matrix p(x.r);
for(int i=0;i<p.r;++i)p.a[i][i]=1;
while(n)
{
if(n&1)p=p*x;
x=x*x,n/=2;
}
return p;
}
void init1()
{
id['A']=0,id['T']=1;
id['C']=2,id['G']=3;
trie[0].tag=0;//向虚节点转移
}
void init2()
{
memset(trie,0,sizeof trie);//只刷一个,之后插入的时候再刷
ans=tot=0;
root=++tot;
}
void insert(int r,char *s)//(root,串),trie树插入
{
int len=strlen(s);
for(int i=0;i<len;i++)
{
if(trie[r].Next[id[s[i]]]==0)
{
trie[r].Next[id[s[i]]]=++tot;
trie[trie[r].Next[id[s[i]]]].tag=0;
}
r=trie[r].Next[id[s[i]]];
}
trie[r].tag=1;//是病毒的结尾
//if(trie[r].tag)printf("%d\n",r);
}
void build(int r)//建ac自动机
{
int head=0,tail=0;
trie[r].fail=r;//root自己连自己
que[tail++]=r;
while(head<tail)
{
r=que[head++];
if(trie[trie[r].fail].tag)trie[r].tag=1;//后缀的最后一个字母是病毒 那么自己也是 importance1
for(int i=0;i<4;i++)
{
int ch=trie[r].Next[i],failp;
if(ch)
{
que[tail++]=ch;
for(failp=trie[r].fail;failp!=root&&trie[failp].Next[i]==0;failp=trie[failp].fail);//向前回跳
if(trie[failp].Next[i]==0||trie[failp].Next[i]==ch)trie[ch].fail=failp;//接到根或者自己
else trie[ch].fail=trie[failp].Next[i];//可以后接
}
else trie[r].Next[i]=trie[trie[r].fail].Next[i];//和fail续一个一模一样的后继 importance2
}
}
}
/*
void debug()
{
for(int i=0;i<M.r;++i)
{
for(int j=0;j<M.c;++j)
printf("%lld ",M.a[i][j]);
puts("");
}
}*/
void buildMatrix()
{
M=Matrix(tot);
for(int i=1;i<=tot;++i)
{
//if(trie[i].tag) printf("%d ",i);
for(int j=0;j<4;++j)
{
if(!trie[i].tag&&!trie[trie[i].Next[j]].tag)
M.a[i-1][max(trie[i].Next[j]-1,0)]++;
}
}
//debug();
}
int main()
{
init1();
while(~scanf("%d%d",&m,&n))
{
init2();
for(int i=0;i<m;i++)
{
scanf("%s",s);
insert(root,s);
}
build(root);
buildMatrix();
M=modpow(M,n,mod);
//debug();
for(int i=0;i<tot;++i)
{
ans+=M.a[0][i];
if(ans>=mod)ans%=mod;
}
printf("%lld\n",ans);
}
return 0;
}