POJ 2778(AC自动机+矩阵快速幂)

        非常有趣的一道题,题目给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;
}

代码借鉴了一位在网上看到代码写的非常简洁的大佬,为表敬意附上链接:

https://www.cnblogs.com/WABoss/p/5167101.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值