大神附图的题解:http://blog.csdn.net/morgan_xww/article/details/7834801
【题解】
将所有病毒串建立成字典树,并标记词尾结点,以下称"非法结点"
那么,我们希望改造一下这棵树,即删掉一些结点,构造一些有向边,使得一个n位字符串相当于从改造图的根走n步,且中途不会形成非法串
对于树上的某个结点u,先允许它走到非法结点,将所有有向边都连好后,再将这些点删去
若 v=ch[u][i] 存在,那么一种情况是从u走到v即可,
若v不存在,不代表不能走,可以求出失配函数,将u与f[u]等价,看ch[f[u][i]是否存在,
方便起见,使用改进过的AC自动机,这样,任意ch[u][i]都存在,若ch[u][i]==0(从root到v构成的串没有后缀是病毒串),那就用root代表u的下一个字母v 。
直接将u到ch[u][i]连边
然后标记剩下非词尾的非法节点,由于字符串s_1~f[n]是串s_1~n的后缀,所以,若f[n]被标记,则n也应被标记,这保证了若s_i~n为病毒串,一定可以被标记
对于有向图上走n步的问题,用邻接矩阵来处理
我们构造这样一个邻接矩阵,若u->v存在,且u,v均合法,则A[u][v]++,这样,A[u][v]就代表u走一步到v有几种方式,且不可能形成非法串
将矩阵A自乘n次,A[u][v]就代表u走n步到v有几种方式
然后,以root为起点,枚举终点,累加答案即可
【代码】
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MOD 100000ll
typedef long long LL;
int ch[105][10],no[105],f[105],q[105],num[105];
char s[15];
int sz=0,L;
struct juzhen
{
LL s[105][105];
juzhen()
{
memset(s,0,sizeof(s));
}
};
juzhen A;
juzhen cheng(juzhen a,juzhen b)
{
juzhen res;
int i,j,k;
for(i=1;i<=L;i++)
for(j=1;j<=L;j++)
{
for(k=1;k<=L;k++)
res.s[i][j]+=a.s[i][k]*b.s[k][j];
res.s[i][j]%=MOD;
}
return res;
}
juzhen ksm(juzhen a,int n)
{
juzhen ans;
if(n==1) return a;
ans=ksm(a,n/2);
ans=cheng(ans,ans);
if(n&1) ans=cheng(ans,a);
return ans;
}
void tj()
{
int u=0,len=strlen(s),i;
for(i=0;i<len;i++)
{
if(s[i]=='A') s[i]=1;
if(s[i]=='T') s[i]=2;
if(s[i]=='C') s[i]=3;
if(s[i]=='G') s[i]=4;
if(ch[u][s[i]]==0) ch[u][s[i]]=++sz;
u=ch[u][s[i]];
}
no[u]=1;
}
void build()
{
int head=0,tail=0,i,u,v;
for(i=1;i<=4;i++)
if(ch[0][i]>0) q[tail++]=ch[0][i];
while(head<tail)
{
u=q[head++];
no[u]|=no[f[u]];
for(i=1;i<=4;i++)
{
v=ch[u][i];
if(v>0)
{
q[tail++]=v;
f[v]=ch[f[u]][i];
}
else ch[u][i]=ch[f[u]][i];
}
}
}
void get_jz()
{
int i,j;
for(i=0;i<=sz;i++)
if(no[i]==0) num[i]=++L;
for(i=0;i<=sz;i++)
if(no[i]==0)
for(j=1;j<=4;j++)
if(no[ch[i][j]]==0) A.s[num[i]][ num[ch[i][j]] ]++;
}
int main()
{
int n,m,i,ans=0;
scanf("%d%d",&m,&n);
for(i=1;i<=m;i++)
{
scanf("%s",s);
tj();
}
build();
get_jz();
A=ksm(A,n);
for(i=0;i<=sz;i++)
if(num[i]>0) ans+=A.s[num[0]][num[i]];
printf("%d",ans%MOD);
return 0;
}