poj 2778 AC自动机+矩阵快速幂

题目链接:https://vjudge.net/problem/POJ-2778

题意:输入n和m表示n个病毒,和一个长为m的字符串,里面只可以有'A','C','G','T' 这四个字符,现在问这个长为m的字符串里面不可以出现任何病毒的情况有多少。

参考的两篇博客:

http://www.cnblogs.com/LQLlulu/p/9344774.html

https://blog.csdn.net/morgan_xww/article/details/7834801

上面的博客写得很好,可以主要看上面的博客(两个一起看)。

写点东西,不一定对。

因为最多10个病毒,每个病毒最多10个字符,所以我们trie树上最多有100个点,其他空的点都指向根节点(也就是说我们把所有的空的节点看成0),这样我们可以到达的点就只有100个(再重复一遍:我们把trie树上面的空节点一律指向根节点,看成0),同时因为有些点是单词的结尾或者他的fail[u]是单词结尾(fail[u]是单词结尾说明当前位置的后缀和fail[u]上的单词相同,这个后缀也不能到达)。

我们一开始先构建一个cnt*cnt的邻接矩阵mat(cnt是trie树上面的节点个数),刚刚构建的mat[i][j]代表从编号为i的点走一步到达编号为j的点的合法(就是不经过单词结尾或fail[u]是单词结尾的点),在离散数学里面有一个结论,就是已知cnt个点之间两两互达(走一步)的可能数量(就是这个cnt*cnt的矩阵已经有了),那么如果我们要计算他们两两之间走n步到达的可能数量,只需要求出矩阵的n次方,这个矩阵的n次方就是点与点之间走n步到达的可能数量,这个可以联想矩阵乘法的计算过程。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<cmath>
#include<vector>
#include<set>
#include<cstdio>
#include<string>
#include<deque> 
using namespace std;
typedef long long LL;
#define eps 1e-8
#define INF 0x3f3f3f3f
#define maxn 105
int trie[maxn][4],fail[maxn],val[maxn];
LL mat[maxn][maxn],f[maxn][maxn],ss[maxn][maxn];
//mat[i][j]记录从i到j有多少种可能,f[0][i]记录以i结束的合法可能,其它维没用,ss用来计算时暂时存储 
int n,m,k,t,cnt; 
char s[12];
void init(){
    memset(trie,0,sizeof(trie));
    memset(fail,0,sizeof(fail));
    memset(val,0,sizeof(val));
    memset(mat,0,sizeof(mat));
    memset(f,0,sizeof(f));
    f[0][0]=1;        //一开始把根节点赋值为1,因为我们只需要第一行,所以可以只把f[0][0]赋值为1
    /*for(int i=0;i<maxn;i++)
    f[i][i]=1; */    //也可以这样 
    cnt=0;
}
int getID(char a){
    if(a=='A')
    return 0;
    if(a=='T')
    return 1;
    if(a=='C')
    return 2;
    if(a=='G')
    return 3;
}
void insert(char *s){
    int root=0;
    for(int i=0;s[i];i++){
        int id=getID(s[i]);
        if(trie[root][id]==0)
        trie[root][id]=++cnt;
        root=trie[root][id];
    } 
    val[root]=1;//标记单词结尾 
}
void build_fail(){
    queue<int>q;
    int root=0;
    for(int i=0;i<4;i++){
        if(trie[root][i])
        q.push(trie[root][i]);
    }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        if(val[fail[u]])//如果这个后缀是一个病毒,那么这个后缀也不可以到达 
        val[u]=1;
        for(int i=0;i<4;i++){
            if(trie[u][i]){
                fail[trie[u][i]]=trie[fail[u]][i];
                q.push(trie[u][i]);
            }else{
                trie[u][i]=trie[fail[u]][i];
            }
        }
    }
}
void build_mat(){//建图 
    for(int i=0;i<=cnt;i++){
        for(int j=0;j<4;j++){
            if(val[trie[i][j]]==0&&val[i]==0)//当前状态或接下来的的状态是病毒都是不可以到达的 
            mat[i][trie[i][j]]++;
        }
    }
}
void muti(LL a[][maxn],LL b[][maxn]){//矩阵快速幂 
    memset(ss,0,sizeof(ss));
    for(int k=0;k<=cnt;k++)
    for(int i=0;i<=cnt;i++){
        for(int j=0;j<=cnt;j++){
            ss[i][j]+=a[i][k]*b[k][j];
            ss[i][j]%=100000;
        }
    }
    for(int i=0;i<=cnt;i++){
        for(int j=0;j<=cnt;j++){
            a[i][j]=ss[i][j];
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF){
        init();
        for(int i=0;i<n;i++){
            scanf("%s",s);
            insert(s);
        }
        build_fail();
        build_mat();//获得走1步时的矩阵 
        while(m){
            if(m&1)
            muti(f,mat);
            muti(mat,mat);
            m>>=1;
        }
        LL ans=0;
        for(int i=0;i<=cnt;i++)
        ans=(ans+f[0][i])%100000;
        printf("%lld\n",ans);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/6262369sss/p/10304410.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值