DNA Sequence POJ - 2778

DNA Sequence  POJ - 2778 

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

题意:首先输入的是n和m,分别代表接下来会给你n个病毒串,问你长度为m的不包含病毒的串有多少种
思路:上次写这道题还是半年前及爱情在家写的,当时尚神指导,轻松AC,然而今天奉老大之命前来复习,读题之后发现,蒙蒙哒~~
首先我们在纸上手动模拟一下或许会对这个题的理解更深刻,我们构建一个AC自动机树,在简单题中,我们已经熟练掌握了建树以及构建fail指针的方式,这道题即是使用到了这个树的一些实际意义。
我们在构建fail指针的时候,加了一部分代码即是
if(en[fail[now]] != 0)
            {
                en[now] = 1;
            }

什么意思呢,我们先来看fail指针的意义,树上每个结点代表一个子串,一个结点的fail指针指向另一个结点,代表的是这个后者是前者的一个后缀,这个在之前应该就是了解的。

然后如果en[fail[now]]!=0 即代表该节点的后缀包含一个病毒串,自然,这个串也是不可以使用的,于是这个点代表的串也是一个病毒串。病毒串是不可以出现的。

接下来我们来讨论为什么AC树是一个状态转移树,每个结点代表的是一个子串,那么从一个串经过next数组到达另一个串,他的意义就是经过一步可以由状态i转换为状态j。有了这句话我们就可以根据现在构建出来的AC树可行的转移路径构建一个转移方程了,部分代码是

    void BuildMatrix()
    {
        memset(mm.m, 0, sizeof(mm.m));
        for(int i=0; i<l; i++)
        {
            for(int j=0; j<4; j++)
            {
                if(nex[i][j]!=-1 && en[i]==0 && en[nex[i][j]]==0)
                {
                    mm.m[i][nex[i][j]] ++;         ///mm数组记录的是从状态i到状态j一步可以到达的方案数  则长度为n即为n步可达 n次幂即可  从根节点出发求和
                }
            }
        }
//        for(int i=0; i<l; i++)
//        {
//            for(int j=0; j<l; j++)
//            {
//                printf("%d  " , mm.m[i][j]);
//            }
//            printf("\n");
//        }
    }///到这应该是没有错误的

三个判断条件分别约束的是,状态i,j都存在,状态i代表的子串不是病毒串,状态j代表的子串不是病毒串。

于是我们可以通过一步从状态i转移到状态j,矩阵记录的是转移的方案数,于是++;

接下来回到我们的问题是要长度为m,即由根节点转移m步,于是对一步可到达矩阵求m次幂可得到m步可到达矩阵,再将第0行加起来,即是从根节点m步可以到达的状态

代码如下:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>

using namespace std;
#define mod 100000

int m, n;
int cnt['Z'+1];

struct MM
{
    long long m[110][110];
};

MM mm;
MM m1, m2;

void init()
{
    cnt['A'] = 0;
    cnt['T'] = 1;
    cnt['C'] = 2;
    cnt['G'] = 3;
}

MM mul(MM a, MM b, int siz)
{
    MM c;
    memset(c.m, 0, sizeof(c.m));
    for(int k=0; k<siz; k++)
    {
        for(int i=0; i<siz; i++)
        {
            if(a.m[i][k]==0) continue;
            for(int j=0; j<siz; j++)
            {
                if(b.m[k][j]==0) continue;
                c.m[i][j] += (a.m[i][k]*b.m[k][j])%mod;
                c.m[i][j] %= mod;
            }
        }
    }
    return c;
}

struct Trie
{
    int nex[110][4];
    int en[110];
    int fail[110];
    int root, l;

    int NewNode()
    {
        for(int i=0; i<4; i++)
        {
            nex[l][i] = -1;
        }
        en[l] = 0;
        l++;
        return l-1;
    }
    void Init()
    {
        l = 0;
        root = NewNode();
    }
    void Insert(char buf[])
    {
        int len = strlen(buf);
        int now = root;
        for(int i=0; i<len; i++)
        {
            if(nex[now][cnt[buf[i]]] == -1)
            {
                nex[now][cnt[buf[i]]] = NewNode();
            }
            now = nex[now][cnt[buf[i]]];
        }
        en[now] = 1;
    }
    void Build()
    {
        queue<int>Q;
        int now;
        fail[root] = root;
        for(int i=0; i<4; i++)
        {
            if(nex[root][i] == -1)
            {
                nex[root][i] = root;
            }
            else
            {
                fail[nex[root][i]] = root;
                Q.push(nex[root][i]);
            }
        }
        while( !Q.empty() )
        {
            now = Q.front();
            Q.pop();
            if(en[fail[now]] != 0)
            {
                en[now] = 1;
            }
            for(int i=0; i<4; i++)
            {
                if(nex[now][i] == -1)
                {
                    nex[now][i] = nex[fail[now]][i];
                }
                else
                {
                    fail[nex[now][i]] = nex[fail[now]][i];
                    Q.push(nex[now][i]);
//                    if(en[fail[nex[now][i]]] != 0)
//                    {
//                        en[nex[now][i]] = 1;///en数组的值为1就不能走 为1代表是一个串的结尾 就不能走 为什么这个字符的fail指向一个en[]不为0的就也要置成不为0呢
//                        ///因为一个病毒串是这个串的后缀 那么这个串虽然不是病毒串 到那时后半部分含有病毒串
//                        ///为什么只要考虑与他直接相连的字符的fail值就可以了呢 因为可以抽象的理解为 fail一定是在他的上面的层 一定不在同一层 因为如果在同一层 则这两个串是一样的 饿就好似同一个串
//                        ///这种情况是不会发生的 同时build()函数是从根节点发出的 保证了上面层的en数组都已经更新完成 具有传递的特质 所以只要考虑直接连接的fail值即可
//                    }
                }
            }
        }
//        for(int i=0; i<l; i++)
//        {
//            printf("%d  %d  ..\n" , en[i] , fail[i]);
//        }
    }
    void BuildMatrix()
    {
        memset(mm.m, 0, sizeof(mm.m));
        for(int i=0; i<l; i++)
        {
            for(int j=0; j<4; j++)
            {
                if(nex[i][j]!=-1 && en[i]==0 && en[nex[i][j]]==0)
                {
                    mm.m[i][nex[i][j]] ++;         ///mm数组记录的是从状态i到状态j一步可以到达的方案数  则长度为n即为n步可达 n次幂即可  从根节点出发求和
                }
            }
        }
//        for(int i=0; i<l; i++)
//        {
//            for(int j=0; j<l; j++)
//            {
//                printf("%d  " , mm.m[i][j]);
//            }
//            printf("\n");
//        }
    }///到这应该是没有错误的
    void Solve()
    {
        MM ans;
        memset(ans.m, 0, sizeof(ans));
        for(int i=0; i<l; i++)
        {
            ans.m[i][i] = 1;    ///0步只能到达本身
        }
        while( n )
        {
            if(n%2==1)
            {
                n--;
                ans = mul(ans, mm, l);
//                for(int i=0; i<l; i++)
//                {
//                    for(int j=0; j<l; j++)
//                    {
//                        printf("%d++", ans.m[i][j]);
//                    }
//                    printf("\n");
//                }
            }
            mm = mul(mm, mm, l);
//            for(int i=0; i<l; i++)
//            {
//                for(int j=0; j<l; j++)
//                {
//                    printf("%d..", mm.m[i][j]);
//                }
//                printf("\n");
//            }
            n /= 2;
        }
        int res;
        res = 0;
        for(int i=0; i<l; i++)
        {
            res = (res+ans.m[0][i])%mod;
        }
        printf("%d\n", res);
    }
};

Trie ac;

void input()
{
    char s[12];
    for(int i=0; i<m; i++)
    {
        scanf("%s", s);
        ac.Insert(s);
    }
}

int main()
{
    init();
    while( scanf("%d%d", &m, &n) != EOF )
    {
        ac.Init();
        input();
        ac.Build();
        ac.BuildMatrix();
        ac.Solve();
    }

    return 0;
}

 

posted @ 2018-08-16 10:04  Flower_Z  阅读(168)  评论(0编辑  收藏  举报