POJ 2778 DNA Sequence AC自动机+DP+矩阵二分加速

题目链接http://poj.org/problem?id=2778

http://vjudge.net/contest/view.action?cid=51731#problem/E


1.题意

告诉你m个字符串,每个字符串长度不超过10,求长度为n的不包括这m个字符串的串的个数。

2.题解

(1)前提要会构造ac自动机;

(2)构造自动机的目的是做DP,DP_i_j表示长度为i且末尾状态为j的串可以由多少种长度为i-1的状态得到 ,那个DP_i_j=西格玛(DP_i-1_k(0<=k<=top)),用g[i][j]表示长度为0的串到长度为n的串到从状态i转移状态j的路径有多少种(路径有多少种就说明在长度为n的串末尾加一个字符后,不产生病毒串,可以有多少种添加字符的方法)。用自动机构造且判断DP_i-1_k(k为所有状态)到DP_i_j这样的转移是否存在,如果k状态是end态(说明走到了病毒上面了),则转移不存在;如果k状态是 !end 态,且k状态的fail态全都是!end态(在构造自动机的时候,每bfs一个状态时就可以每次判断一次他的fail转移是不是end态,如果是end态,就说明他的后缀有病毒,不管他到底是不是end态,也都要改变成end态),如果next[k][c]有边(不管是fail转移到j状态,还是接受后转移到j状态都算)可以到j状态,则转移存在,那就用矩阵把对于的g[k][j]加一。

(3)初次求得g[][]矩阵时,它表示从字符串长度0到字符串长度1,状态i到状态j之间的转移可以有几种,只要求出g[][]的n次方,就可以知道从字符串长度0到字符串长度n,状态i到状态j之间的转移可以有几种;


code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
/**
5 4
AAA
ATA
T
TA
TAA
初始矩阵
2 1 0 0 0 0 0 0 0
2 0 1 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0
处理后的矩阵
52 18 6 0 0 0 0 0 0
48 16 6 0 0 0 0 0 0
36 12 4 0 0 0 0 0 0
36 12 4 0 0 0 0 0 0
36 12 4 0 0 0 0 0 0
36 12 4 0 0 0 0 0 0
36 12 4 0 0 0 0 0 0
36 12 4 0 0 0 0 0 0
36 12 4 0 0 0 0 0 0

76
**/
#define Clear(x) memset(x,0,sizeof x)
typedef long long LL;
const int MAXN=108,MAX=4,MOD=100000;
int sz;
struct Mat{
    int m[MAXN][MAXN];
    Mat(){Clear(m);}
    Mat(int x){
        Clear(m);
        for(int i=0;i<sz;i++)
            m[i][i]=x;
    }
};
Mat operator*(Mat a,Mat b){
    Mat ret;
    int i,j,k;
    for(i=0;i<sz;i++)
        for(j=0;j<sz;j++){
            if(a.m[i][j]==0) continue;
            for(k=0;k<sz;k++){
                int tmp=((LL)a.m[i][j]*b.m[j][k])%MOD;
                ret.m[i][k]=(ret.m[i][k]+tmp)%MOD;
            }
        }
    return ret;
}
Mat Pow(Mat a,int b){
    Mat ret(1);
    while(b){
        if(b&1)ret=ret*a;
        a=a*a;
        b>>=1;
    }
    return ret;
}
Mat dp;

int ch[MAXN][MAX],f[MAXN],top;
bool end[MAXN];
int idx(char ch){
    switch(ch){
        case 'A':return 0;
        case 'C':return 1;
        case 'G':return 2;
    }
    return 3;//T
}
void init(){
    top=0;
    Clear(end);
    Clear(f);Clear(ch);
}
int NewNode(){return ++top;}
void insert(char *s,int num){
    int x=0,n=strlen(s);
    for(int i=0;i<n;i++){
        int c=idx(s[i]);
        if(!ch[x][c]) ch[x][c]=NewNode();
        x=ch[x][c];
    }
    end[x]=true;
}
void getFail(){
    queue<int>Q;
    for(int c=0;c<MAX;c++){
        int &u=ch[0][c];
        if(u)Q.push(u);
    }
    while(!Q.empty()){
        int r=Q.front();Q.pop();
        if(end[f[r]])//!!!
            end[r]=true;
        for(int c=0;c<MAX;c++){
            int u=ch[r][c];
            int v=f[r];
            if(!u){ch[r][c]=ch[v][c];continue;}
            Q.push(u);
            while(v && !ch[v][c]) v=f[v];
            f[u]=ch[v][c];
        }
    }
}
void getDP(){
    Clear(dp.m);
    for(int i=0;i<=top;i++){
        for(int j=0;j<MAX;j++){
            if(!end[ch[i][j]]) dp.m[i][ch[i][j]]++;
        }
    }
}
int main()
{
//    freopen("data.in","r",stdin);
    int n,m;
    char P[20];
    while(scanf("%d%d",&n,&m)==2){
        init();
        for(int i=1;i<=n;i++){
            scanf("%s",P);
            insert(P,i);
        }
        getFail();
        sz=top+1;
        getDP();
        dp=Pow(dp,m);
        int ans=0;
        for(int i=0;i<sz;i++){
            ans+=dp.m[0][i];
            ans%=MOD;
        }
        printf("%d\n",ans);
    }
    return 0;
}


你可能感兴趣的:(dp,AC自动机,矩阵二分)