poj2778 DNA Sequence(AC自动机+矩阵快速幂 )

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值