非常有趣的一道题,题目给m个病毒串,问不包含病毒串的长度n的DNA片段有几个。
用病毒串构造AC自动机,用fail指针跑出字符串状态间的到达关系和危险点,危险点即到达即代表某串含有病毒的点。
用AC自动机构造出矩阵,初始矩阵中的mat[i][j]表示i点走一步到达j点的方法,由于危险点不可到达所以危险点的行和列为0。例如初始有0->1的1种方法,0->2的一种方法,0->3的一种方法,而矩阵相乘后,本身矩阵中1->4有一种方法,2->4有一种方法,3->4有一种方法,不也就可以说从0出发,两步到达4有3种方法,这里即左矩阵0行(出发行)和右矩阵4列(到达列)相乘。
看了几篇别人的博客,刚开始一直在纠结为何只有后缀含有病毒的点算危险点,儿其后的点则不算,其实,只要算出后缀含有的点即可,因为矩阵是通过相互到达传递的,其后的点只有这个危险点能到达,危险点的行和列全是0,则其后的点也不会参与运算。可以这样说,0行里危险点的列和其后的列从来都是0,因为不能到达。
给出测试案例:
2 1
ACG
C
其中矩阵应该为
2 1 0 0 0
2 1 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define ll long long
using namespace std;
const int maxn = 110;
const int mod = 100000;
int nxt[maxn][4],fail[maxn],tot;
bool flag[maxn];
int idx[200];
void add(char buf[]){
int len=strlen(buf);
int now=0;
for(int i=0;i<len;i++){
int k=idx[buf[i]];
if(nxt[now][k]==0) nxt[now][k]=++tot;
now=nxt[now][k];
}
flag[now]=1;
}
void getmat(){
memset(fail,0,sizeof(fail));
queue<int>que;
for(int i=0;i<4;i++){
if(nxt[0][i]) que.push(nxt[0][i]);
}
while(!que.empty()){
int now=que.front(); que.pop();
for(int i=0;i<4;i++){
if(!nxt[now][i])
nxt[now][i]=nxt[fail[now]][i];
else{
que.push(nxt[now][i]);
fail[nxt[now][i]]=nxt[fail[now]][i];
}
flag[nxt[now][i]]|=flag[nxt[fail[now]][i]];
//通过构建fail数组找出树上所有的危险节点,如果我的fail(后缀)危险,那我也危险
}
}
}
struct Mat{
ll mat[111][111];
Mat(){
memset(mat,0,sizeof(mat));
}
};
Mat operator*(const Mat &m1,const Mat &m2){
Mat m;
for(int i=0;i<=tot;i++){
for(int j=0;j<=tot;j++){
for(int k=0;k<=tot;k++){
m.mat[i][j]+=m1.mat[i][k]*m2.mat[k][j];
m.mat[i][j]%=mod;
}
}
}
return m;
}
int main()
{
idx['A']=0,idx['C']=1,idx['T']=2,idx['G']=3;
char str[11];
int m,n;
while(~scanf("%d%d",&m,&n)){
tot=0;
memset(flag,0,sizeof(flag));
memset(nxt,0,sizeof(nxt));
while(m--){
scanf("%s",str);
add(str);
}
getmat();
Mat e,x;//e为单位矩阵,x为trie构建的矩阵
for(int i=0;i<=tot;i++) e.mat[i][i]=1;
for(int i=0;i<=tot;i++){
if(flag[i]) continue;
for(int j=0;j<4;j++){
if(flag[nxt[i][j]]) continue;
++x.mat[i][nxt[i][j]];
}
}
while(n){
if(n&1) e=e*x;
x=x*x;
n>>=1;
}
ll ans=0;
for(int i=0;i<=tot;i++){
ans+=e.mat[0][i];
ans%=100000;
}
printf("%lld\n",ans);
}
return 0;
}
代码借鉴了一位在网上看到代码写的非常简洁的大佬,为表敬意附上链接: