~题意:给定m(1<=m<=10)个字符串(仅含有A T C G,长度不超过10),和正整数n(1<=n<=2e9),问有多少种仅包含A T C G 的长度为n的字符串,满足不包含这m个字符串中的任意一个作为子串。
•样例m=4,n=3,{“AA”,”AT”,”AC”,”AG”}
•答案为36,表示有36种长度为3的序列可以不包含疾病
思路:这题需要知道一个重要的知识,首先建ac自动机可以得到一个有向图,
如图所示就是插入"ACG" 和”C"这连个字符串之后的trie图。每条边都是一个字符。
得到这个有向图后,问题就可以等价为, 求出这个trie图中从根节点0出发, 经过n条边(但不经过病毒路径)到达某个点的所有方案数之和。 (因为字符是边,是自动机中状态转移的凭借物)
然后我们把这个图用邻接矩阵M表示,M[i][j]的求法就是,从第i个节点出发,沿着所有孩子节点(此题只有AGCT四个)代表的路径得到终点j,然后M[i][j]++,而且这些路径在求fail指针的时候已经求好了。 这样得到的M矩阵,M[i][j]的意义就是从i 只经过一条边 到达j的方案数。(当然,如果i或目的地j是某个病毒后缀, 那M[i][j]就不加1,一直为0)。
那我们求出这个M矩阵有啥用呢? 别急,我们先考虑M²,令C=M*M, 由矩阵乘法可知C[i][j]=ΣM[i][k]*M[k][j] ( k从0枚举到最后),很明显,C[i][j]表示从i到j有且经过两条边(<i,k>、<k,j>)的方案数, 以此类推,设M的n次方为矩阵F,F[i][j]就是i到j刚好经过n条边的方案数,那么我们要求的问题,就是ΣF[0][k] k从0枚举到最后。
值得注意的是 n的范围很大,求F的时候需要用矩阵快速幂,否则tle。
语言表达能力有些拉跨= =、 具体的细节还是见代码吧。 大佬轻拍~
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define INF 0x3f3f3f
#define LL long long
const int maxn=105;
typedef LL matrix[maxn][maxn];
matrix mx,ans;//把trie图的邻接矩阵求出来存在mx里,用矩阵快速幂求mx的n次方,答案存于ans
int book[128]={0};//预处理A T C G 分别为0 1 2 3
int tree[maxn][4];//字典树
bool isstr[maxn];//以i节点结尾是否为病毒串
int tot;//节点总数
int fail[maxn];
int m,n;
char s1[15];
void Init()
{
book['A']=0;
book['T']=1;
book['C']=2;
book['G']=3;
memset(isstr,false,sizeof(false));
}
void Insert(char *s)
{
int len=strlen(s),root=0;
for(int i=0;i<len;i++)
{
int id=book[s[i]];
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
isstr[root]=true;
}
void getfail()
{
queue<int> q;
for(int i=0;i<4;i++)
{
if(tree[0][i])
{
q.push(tree[0][i]);
//fail[tree[0][i]]=0;
}
//else tree[0][i]=0
}
while(!q.empty())
{
int v=q.front();
q.pop();
if(isstr[fail[v]]) isstr[v]=true;//注意,如果有后缀是病毒,那这个也要不得
for(int i=0;i<4;i++)
{
if(tree[v][i])
{
fail[tree[v][i]]=tree[fail[v]][i];
q.push(tree[v][i]);
}
else tree[v][i]=tree[fail[v]][i];
}
}
}
void get_matrix()//获得trie图的邻接矩阵,每个点有4条路径,目的地就是4个孩子节点存的序号
{
for(int i=0;i<=tot;i++)
{
for(int j=0;j<4;j++)
{
if(!isstr[i] && !isstr[tree[i][j]])//有病毒序列的串舍掉
mx[i][tree[i][j]]++;//i到tree[i][j]长度为一条边的路径+1
}
}
}
void matrix_mutply(matrix m1,matrix m2,matrix temp)//矩阵乘法,m1*m2,结果存于ret
{
for(int i=0;i<=tot;i++)
{
for(int j=0;j<=tot;j++)
{
temp[i][j]=0;
for(int k=0;k<=tot;k++)
{
temp[i][j]+=(m1[i][k]*m2[k][j]);
}
temp[i][j]%=100000;
}
}
}
void matrix_quick(int N)//求mx的N次方,结果存于ans
{
matrix temp;
for(int i=0;i<=tot;i++) ans[i][i]=1;//初始化成单位矩阵
while(N)
{
if(N&1)
{
matrix_mutply(ans,mx,temp);
swap(ans,temp);
}
matrix_mutply(mx,mx,temp);
swap(mx,temp);
N>>=1;
}
}
int main()
{
Init();
scanf("%d%d",&m,&n);
while(m--)
{
scanf("%s",s1);
Insert(s1);
}
getfail();
get_matrix();
matrix_quick(n);
int sum=0;
for(int i=0;i<=tot;i++)
{
sum=(sum+ans[0][i]);
}
printf("%d\n",sum%100000);
system("pause");
return 0;
}