大神附图的题解: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; }