AC自動機+矩陣快速冪

POJ 2778 DNA序列

題意:給n條病毒DNA序列,然後問所有長度爲L的DNA序列中,不包含病毒的序列有多少條。

分析:首先將病毒的DNA序列建AC自動機,然後建fail指針。將每個點編號作爲狀態,然後可以得每個點走一步後的狀態,然後長度爲L,相當於走了L步,矩陣快速冪即可。舉個栗子
比如病毒是{AC,C}
圖好難畫啊,邊就不連了。。
這裏寫圖片描述

從0點,到0的方式有2個(G,T),到1的方式有1個(A),到2的方式有0個,到3的方式有1個(C)
從1點,到0的方式有2個(G,T),到1的方式有1個(A),到2的方式有1個,到3的方式有0個
從2點,到0的方式有2個(G,T),到1的方式有1個,到2的方式有0個,到3的方式有1個
從3點,到0的方式有2個(G,T),到1的方式有1個(A),到2的方式有0個,到3的方式有1個(C)

可以構造矩陣M
2,1,0,1
2,1,1,0
2,1,0,1
2,1,0,1
M[i][j]:表示從i狀態到j狀態的走一步的方式總數(就是我們上面列舉的呀)
我們需要求長度爲L的DNA序列有多少沒有病毒,也就是從根開始走L步,問不經過病毒的路徑條數
M矩陣表示走一步的狀態,所以ML 就表示走L步的所有情況,那麼怎麼確定沒有走過病毒呢,只需要在構造矩陣的時候,將經過病毒的狀態標爲0,(也就是不走這條路)就行了

上面的矩陣去掉病毒後是
2,1,0,0
2,1,0,0
0,0,0,0
0,0,0,0

ps:開始用C++交T,然後用G++交,剛好1000ms…時限是1000ms….
然後看了一下優化,在矩陣乘法的時候,減少取模,也就是每一行取一次。時間就到188ms了..

using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
const int maxn = 5e5+10,mod=1e5;

int getid(char c)
{
    if(c=='A') return 0;
    else if(c=='C') return 1;
    else if(c=='T') return 2;
    else if(c=='G') return 3;
}
struct matrix
{
    ll mat[105][105];
    matrix()
    {
        mem(mat,0);
    }
};

struct Trie
{
    int next[maxn][5],fail[maxn],ed[maxn];
    int root,L;
    int newnode()
    {
        for(int i=0;i<4;i++)
            next[L][i]=-1;
        ed[L++]=0;
        return L-1;
    }
    void init()
    {
        L=0;
        root=newnode();
    }
    void sert(char buf[])
    {
        int len=strlen(buf);
        int now=root;
        for(int i=0;i<len;i++)
        {
            int id=getid(buf[i]);
            if(next[now][id]==-1)
                next[now][id]=newnode();
            now=next[now][id];
        }
        ed[now]=1;
    }
    void buildfail()
    {
        queue<int> Q;
        fail[root]=root;
        for(int i=0;i<4;i++)
            if(next[root][i] == -1)
                  next[root][i]=root;//這裏爲什麼要改變next?爲了之後query的時候
            else {
                fail[next[root][i]]=root;
                Q.push(next[root][i]);
            }
        while(!Q.empty())
        {
            int now=Q.front();
            Q.pop();
            if(ed[fail[now]]) ed[now]=1;
            for(int i=0;i<4;i++){
                if(ed[next[fail[now]][i]])
                   ed[next[now][i]]=1;
                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]);
                }
            }
        }
    }
    int query(char *str)
    {
        int len=strlen(str);
        int now=root;
        int res=0;
        for(int i=0;i<len;i++)
        {
            int id=getid(str[i]),temp;
            now=next[now][id],temp=now;
            while(temp!=root)
            {
                res+=ed[temp];
                ed[temp]=0;
                temp=fail[temp];
            }
        }
        return res;
    }
    matrix buildM()
    {
        matrix A;
        for(int i=0;i<L;i++)
        {
            for(int j=0;j<4;j++)
                if(!ed[i]&&!ed[next[i][j]])
                {
                    A.mat[i][next[i][j]]++;
                }
        }
        return A;
    }
    int getL()
    {
        return L;
    }
};
int sz;
matrix mul(matrix A,matrix B)
{
    matrix c;
    for(int i=0;i<sz;i++)
    {
        for(int j=0;j<sz;j++)
        {
            for(int k=0;k<sz;k++)
                c.mat[i][j]=c.mat[i][j]+A.mat[i][k]*B.mat[k][j];
            c.mat[i][j]=c.mat[i][j]%mod;
        }
    }
    return c;
}
matrix qk(matrix A,ll n)
{
    matrix B;
    for(int i=0;i<sz;i++) B.mat[i][i]=1;
    while(n>0)
    {
        if(n&1) B=mul(A,B);
        A=mul(A,A);
        n=n/2;
    }
    return B;
}
char s1[15];
Trie ac;
int main()
{
    int n,m;
    ac.init();
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%s",s1);
        ac.sert(s1);
    }
    ac.buildfail();
    matrix A=ac.buildM();
    sz=ac.getL();
    A=qk(A,m);
    ll ans=0;
    for(int i=0;i<sz;i++) ans=(ans+A.mat[0][i])%mod;
    printf("%lld\n",ans);
    return 0;
}

考研路茫茫——單詞情結 HDU - 2243
題意:給一些詞根(字符串),然後問包含這些詞根的長度不超過L的單詞有多少個

分析:這是一個求和的呀。長度爲1,2,…L都要加起來。
因爲不包含這些詞根的數量好算點,根據上面的啓示,我們可以將問題轉化爲求總單詞數-不包含這些詞根的單詞總數
總單詞數=26+262+263+...+26L
不包含這些詞根的單詞數和上面那道題的求法一模一樣。
不包含這些詞根的單詞總數也是一個求和,設構造矩陣爲A,那麼就是A1+A2+A3+....+AL

這個求和怎麼算比較快?就是構造下面這個矩陣,E爲單位矩陣
A E
0 E
然後sum就爲這個矩陣L次方後0行的上面兩個矩陣的和-E,或者這個矩陣L+1次方後的右上角那個矩陣然後-E

對了,關於mod2e64,long long 的範圍可以到2e63-1,unsigned long long 可以到2e64-1,直接用unsigned計算,就相當於取模啦

#define ll unsigned long long
#define mem(a,b) memset(a,b,sizeof(a))
const int maxn = 105;

struct matrix
{
    ll mat[105][105];
    matrix()
    {
        mem(mat,0);
    }
};

struct Trie
{
    int next[maxn][30],fail[maxn],ed[maxn];
    int root,L;
    int newnode()
    {
        for(int i=0;i<26;i++)
            next[L][i]=-1;
        ed[L++]=0;
        return L-1;
    }
    void init()
    {
        L=0;
        root=newnode();
    }
    void sert(char buf[])
    {
        int len=strlen(buf);
        int now=root;
        for(int i=0;i<len;i++)
        {
            int id=buf[i]-'a';
            if(next[now][id]==-1)
                next[now][id]=newnode();
            now=next[now][id];
        }
        ed[now]=1;
    }
    void buildfail()
    {
        queue<int> Q;
        fail[root]=root;
        for(int i=0;i<26;i++)
            if(next[root][i] == -1)
                  next[root][i]=root;//這裏爲什麼要改變next?爲了之後query的時候
            else {
                fail[next[root][i]]=root;
                Q.push(next[root][i]);
            }
        while(!Q.empty())
        {
            int now=Q.front();
            Q.pop();
            if(ed[fail[now]]) ed[now]=1;
            for(int i=0;i<26;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]);
                }
            }
        }
    }
    matrix buildM()
    {
        matrix A;
        for(int i=0;i<L;i++)
        {
            for(int j=0;j<26;j++)
                if(!ed[next[i][j]])
                {
                    A.mat[i][next[i][j]]++;
                }
        }
        for(int i=0;i<L;i++)
            A.mat[i][i+L]=1;
        for(int i=L;i<L*2;i++)
            A.mat[i][i]=1;
        return A;
    }
    int getL()
    {
        return 2*L;
    }
};
matrix mul(matrix A,matrix B,int sz)
{
    matrix c;
    for(int i=0;i<sz;i++)
    {
        for(int j=0;j<sz;j++)
        {
            for(int k=0;k<sz;k++)
                c.mat[i][j]=c.mat[i][j]+A.mat[i][k]*B.mat[k][j];
        }
    }
    return c;
}
matrix qk(matrix A,ll n,int sz)
{
    matrix B;
    for(int i=0;i<sz;i++) B.mat[i][i]=1;
    while(n>0)
    {
        if(n&1) B=mul(A,B,sz);
        A=mul(A,A,sz);
        n=n/2;
    }
    return B;
}
char s1[15];
Trie ac;
int main()
{
    ll n,m;
    while(cin>>n>>m){
    ac.init();
    for(int i=0;i<n;i++)
    {
        scanf("%s",s1);
        ac.sert(s1);
    }
    ac.buildfail();
    matrix A=ac.buildM();
    int sz=ac.getL();
    A=qk(A,m+1,sz);
    ll res=0;
    for(int i=sz/2;i<sz;i++) res+=A.mat[0][i];
    res--;
    matrix B;
    B.mat[0][0]=26;B.mat[0][1]=B.mat[1][1]=1;
    B=qk(B,m,2);
    ll ans=B.mat[0][0]+B.mat[0][1];
    ans--;
    ans=ans-res;
    cout<<ans<<endl;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章