poj--2778DNA Sequence+AC自動機+矩陣快速冪

acm_lkl發表於2020-04-04

題目連結:點選進入
如果我們先將所有的病毒字串構建成一棵trie,然後構建一個n長的字串的過程可以看成是從這顆trie樹根節點出發走n步的一個過程,為了不含任何的病毒,則在走的過程中不能經過任何的病毒節點。考慮只走一步的過程,則我們可以得到一個矩陣m[i][j],表示從節點i到節點j有多少種方式。那麼這個矩陣的n次冪就是表示走n步的情況,然後也就可以得到答案了。
問題在於如何獲得這個走一步方案數的矩陣,我們可以利用構造ac自動機的過程,將所有的病毒葉子節點以及以這些節點做字尾的節點標記為病毒節點,然後就可以通過檢查每個節點的所有兒子節點得到到達他們的方案數了。更具體的思路見程式碼

程式碼如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;

#define ll long long

struct Matrix
{
    ll mat[140][140];
    int n;
    Matrix(){};
    Matrix(int n1)
    {
        n=n1;
        for(int i=0;i<n;i++)
          for(int j=0;j<n;j++)
             mat[i][j]=0;
    }
    Matrix operator *(const Matrix &b) const
    {
        Matrix ret=Matrix(n);
        for(int i=0;i<n;i++)
          for(int j=0;j<n;j++)
          {
            for(int k=0;k<n;k++)
               ret.mat[i][j]+=mat[i][k]*b.mat[k][j];
            ret.mat[i][j]%=100000;
          }
        return ret;
    }
};

Matrix matrixPow(Matrix matrix,int n)
{
    Matrix ret = Matrix(matrix.n);
    for(int i=0;i<matrix.n;i++)
      ret.mat[i][i]=1;
    while(n)
    {
        if(n&1) ret=ret*matrix;
        matrix=matrix*matrix;
        n=n>>1;
    }
    return ret;
}

int getIndex(char x)
{
   if(x=='A') return 0;
   if(x=='T') return 1;
   if(x=='C') return 2;
   if(x=='G') return 3;
}

struct Trie
{
    int next[140][4],fail[140],flag[140];
    int root,L;
    int newnode()
    {
        memset(next[L],-1,sizeof(next[L]));
        flag[L++]=0;
        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++)
        {
            int index=getIndex(buf[i]);
            if(next[now][index]==-1)
               next[now][index]=newnode();
            now=next[now][index];
        }
        flag[now]++;
    }
    void build()
    {
        queue<int>Q;
        fail[root]=root;
        for(int i=0;i<4;i++)
        {
            if(next[root][i]==-1)
               next[root][i]=root;
            else
            {
                fail[next[root][i]]=root;
                Q.push(next[root][i]);
            }
        }
        while(!Q.empty())
        {
            int now=Q.front();
            Q.pop();
            ///如果當前字串的字尾節點是病毒,則當前字串也是病毒
            ///這是因為更新的時候會先更新淺層的然後再更新深層的
            ///所以要用淺層的標記去更新深層的
            if(flag[fail[now]])
              flag[now]++;
            for(int i=0;i<4;i++)
            {
                if(next[now][i]==-1)
                  next[now][i]=next[fail[now]][i];
                else
                {
                    fail[next[now][i]]=next[fail[now]][i];
                    Q.push(next[now][i]);
                }
            }
        }
    }
    ///獲得對應的狀態轉移矩陣
    ///對trie中的每個節點都檢查其所有兒子節點
    void getMatrix(Matrix &matrix)
    {
        for(int i=0;i<L;i++)
          for(int j=0;j<4;j++)
             if(!flag[i]&&!flag[next[i][j]])
                matrix.mat[i][next[i][j]]++;
    }
};

Trie ac;
char str[20];

int main()
{
    int m,n;
    //freopen("in.txt","r",stdin);
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        Matrix matrix = Matrix(50);
        ac.init();
        for(int i=0;i<m;i++)
        {
            scanf("%s",str);
            ac.insert(str);
        }
        ac.build();
        ac.getMatrix(matrix);
        matrix=matrixPow(matrix,n);
        ll ans=0;
        //check(matrix);
        for(int i=0;i<ac.L;i++)
          ans+=matrix.mat[0][i];
        printf("%lld\n",ans%100000);
    }
  return 0;
}

相關文章