DNA Sequence POJ - 2778 小白详解(AC自动机+矩阵快速幂)

该博客介绍了如何使用AC自动机和矩阵快速幂算法解决不包含特定子串的DNA序列计数问题。通过构建trie图并转化为邻接矩阵,博主详细解释了如何计算从根节点出发,经过指定数量边但不经过病毒路径的方案总数。最后,博主提到由于问题规模大,需采用矩阵快速幂优化计算过程。
摘要由CSDN通过智能技术生成

题目链接

~题意:给定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;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值