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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值