POJ2778 DNA Sequence(ac自动机构造dp矩阵)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37025443/article/details/84873629

DNA Sequence

Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 20081   Accepted: 7631

Description

It's well known that DNA Sequence is a sequence only contains A, C, T and G, and it's very useful to analyze a segment of DNA Sequence,For example, if a animal's DNA sequence contains segment ATC then it may mean that the animal may have a genetic disease. Until now scientists have found several those segments, the problem is how many kinds of DNA sequences of a species don't contain those segments.

Suppose that DNA sequences of a species is a sequence that consist of A, C, T and G,and the length of sequences is a given integer n.

Input

First line contains two integer m (0 <= m <= 10), n (1 <= n <=2000000000). Here, m is the number of genetic disease segment, and n is the length of sequences.

Next m lines each line contain a DNA genetic disease segment, and length of these segments is not larger than 10.

Output

An integer, the number of DNA sequences, mod 100000.

Sample Input

4 3
AT
AC
AG
AA

Sample Output

36

Source

POJ Monthly--2006.03.26,dodo

题意:

给你m个字符串,让你构造长度为n的字符串,使得不包含上述字符串。问你这样的字符串有多少个?

解析:

经典题目8 给定一个有向图,问从A点恰好走k步(允许重复经过边)到达B点的方案数mod p的值
    把给定的图转为邻接矩阵,即A(i,j)=1当且仅当存在一条边i->j。令C=A*A,那么C(i,j)=ΣA(i,k)*A(k,j),实际上就等于从点i到点j恰好经过2条边的路径数(枚举k为中转点)。类似地,C*A的第i行第j列就表示从i到j经过3条边的路径数。同理,如果要求经过k步的路径数,我们只需要二分求出A^k即可。

 以

3 5
CAAG
AA
AGA

为例

ac自动机

 

构建的图 

与0不可达的连通块的边我就不画了

便于理解的图就是下面这样的,但是实际构建出来的是上面那个图

根据ac自动机构造矩阵有几点
1.所有空孩子都要重新指向根节点。这一步代表重新进行匹配。
表示前面已经构造了一个不包含任何病毒串的串,现在继续开始构造。
2.所有end=1的结点x都要将end=1更新到所有fail指向x的点
即一个病毒串要将包含这个病毒串的其他病毒串相应的位置断开
因为在ac自动机上走时,对于一个串虽然没有走到这个串的结尾,但是
当走到中间的时候,就发现这个串的子串中有其他完整的病毒串,那么此时就不能再走下去了,这个点后面的点都是不可达的,就将这条边断掉,将这个串(树上的一条链)分成两个连通块。最后我们其实得到的都是每一个病毒串最多能走到的合法的最长前缀例如上图的CAAG和AA
3.最后我们只需要在包含根节点的那个连通块进行dp,这样无论如何都不会出现构造出包含病毒串的串。
4.对于一开始构造的矩阵C[i,j]除了表示i->j走一步的方案数之外,还代表着从根匹配到i这一个点得到的串再走一步(加上j表示的字符)是不是合法的。
最后注意所有的串再ac自动机上都是一条链,所以也保证了算法的正确性。主要保证了第2点。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<set>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
typedef  long long ll;
using namespace std;
const int N = 1e3+100;
const int MAX = 5E4+100;
const int C = 'A';
const int NUM = 4;
const int MOD  = 100000;


struct Tree//字典树
{
    int fail;//失配指针
    int vis[NUM];//子节点的位置
    int end;//标记有几个单词以这个节点结尾
    int id;
}AC[MAX];//Trie树


typedef struct {

    int m[105][105];
    int sizx,sizy;

}Matrix;


inline Matrix Mul(Matrix a, Matrix b)
{

    Matrix c;

    memset(c.m, 0, sizeof(c.m));
    c.sizx=a.sizx;
    c.sizy=b.sizy;
    for (int i = 0; i < a.sizx; i++)
    {
        for (int j = 0; j < b.sizy; j++)
        {
            for (int k = 0; k < a.sizy; k++)
            {
                c.m[i][j] = (c.m[i][j] + (1ll * a.m[i][k] * b.m[k][j])%MOD  )%MOD ;
            }
        }
    }
    return c;
}

inline Matrix fastm(Matrix a, ll num)
{
    Matrix res;
    memset(res.m, 0, sizeof(res.m));
    res.sizx=a.sizx;
    res.sizy=a.sizy;
    for(int i=0;i<a.sizx;i++)
        res.m[i][i]=1;
    while (num)
    {
        if (num & 1)
            res = Mul(res, a);
        num >>= 1;
        a = Mul(a, a);
    }
    return res;
}


inline int _trans(char c)
{
    switch (c)
        {
        case 'A':
            return 0;
        case 'T':
            return 1;
        case 'C':
            return 2;

        }
        return 3;
}



int cnt=0;//Trie的指针
inline void Build(char *s)
{
    int l=strlen(s);
    int now=0;//字典树的当前指针
    for(int i=0;i<l;++i)//构造Trie树
    {
        if(AC[now].vis[_trans(s[i])]==0)//Trie树没有这个子节点
            AC[now].vis[_trans(s[i])]=++cnt;//构造出来
        now=AC[now].vis[_trans(s[i])];//向下构造
    }
    AC[now].end++;//标记单词结尾
}


void Get_fail()//构造fail指针
{
    queue<int> Q;//队列
    AC[0].fail=0;  //!!!
    for(int i=0;i<NUM;++i)//第二层的fail指针提前处理一下
    {
        if(AC[0].vis[i]!=0)
        {
            AC[AC[0].vis[i]].fail=0;//指向根节点
            Q.push(AC[0].vis[i]);//压入队列
        }
        else
        {
            AC[0].vis[i]=0;  //指向根节点!!
        }

    }
    while(!Q.empty())//BFS求fail指针
    {
        int u=Q.front();
        Q.pop();
        AC[u].end|=AC[AC[u].fail].end; //!!将单词的结尾都更新到各个被包含的子串中
        for(int i=0;i<NUM;++i)//枚举所有子节点
        {
            if(AC[u].vis[i]!=0)//存在这个子节点
            {
                AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];
                //子节点的fail指针指向当前节点的
                //fail指针所指向的节点的相同子节点
                Q.push(AC[u].vis[i]);//压入队列
            }
            else//不存在这个子节点
                AC[u].vis[i]=AC[AC[u].fail].vis[i];
            //当前节点的这个子节点指向当
            //前节点fail指针的这个子节点

        }
    }
}

Matrix getMatrix_ac()  //已经将所有的虚结点都指向root
{
    Matrix c;
    memset(c.m,0,sizeof(c.m));
    c.sizx=cnt+1;
    c.sizy=cnt+1;
    for(int i=0;i<=cnt;i++)
    {
        for(int j=0;j<NUM;j++)
        {
            if(!AC[AC[i].vis[j]].end)
                c.m[i][AC[i].vis[j]]++;
        }
    }
    return c;
}


char s[100];
int main()
{
    int m,n;
    scanf("%d%d",&m,&n);
    cnt=0;
    getchar();
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s);
        Build(s);
    }
    AC[0].fail = 0;//结束标志
    Get_fail();//求出失配指针
    Matrix ans=fastm(getMatrix_ac(),n);
    ll res=0;
    for(int i=0;i<=cnt;i++)
        res=(res+ans.m[0][i])%MOD;
    printf("%lld\n",res);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37025443/article/details/84873629