业余ACMer笔记·AC自动机

分组被分配到了主攻字符串,计算几何方面的问题,接下来一段时间主要看这些方面的知识了。

这次三个题BZOJ 1030,POJ 2778,hdu2243是同一种套路,但数据范围和题目要求不同,在这里整理一下。

BZOJ1030: [JSOI2007]文本生成器

很遗憾BZOJ停运了,所以只能在这里看题面了参考博客

代码:
主要思想,先枚举所有可能性,然后去掉不合理的方案数。
由于数据量较小,可以直接使用DP
代码来自参考博客

#include 
#define maxn 105
using namespace std;
int dp[maxn][7000];
char s[500];
const int mod=10007;
int n,m;
struct Trie{
    int next[10000][26],fail[10000],End[10000],id,root;
    int newnode(){
        for(int i=0;i<26;i++){
            next[id][i]=-1;
        }
        End[id]=0;
        return id++;
    }
    void init(){
        id=0;
        root=newnode();
    }
    void Insert(char *str){
        int len=strlen(str);
        int now=root;
        for(int i=0;i<len;i++){
            if(next[now][str[i]-'A']==-1){
                next[now][str[i]-'A']=newnode();
            }
            now=next[now][str[i]-'A'];
        }
        End[now]=1;
    }
    void build(){
        queue<int>que;
        fail[root]=root;
        for(int i=0;i<26;i++){
            if(next[root][i]==-1){
                next[root][i]=root;
            }
            else{
                fail[next[root][i]]=root;
                que.push(next[root][i]);
            }
        }
        while(!que.empty()){
            int now=que.front();
            que.pop();
            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];
                    que.push(next[now][i]);
                }
            }
            End[now]|=End[fail[now]];//终点标志也需要转移
        }
    }
}ac;
int powmod(int a,int n)//普通快速幂
{
    int res=1;
    while(n){
        if(n&1) res=res*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return res;
}
void solve()
{
    dp[0][0]=1;
    for(int i=1;i<=m;i++)
    {
        for(int j=0;j<=ac.id;j++)
        {
            if(ac.End[j]) continue;//如果当前位为某一个串的终点
            for(int k=0;k<26;k++)
            {
                if(ac.End[ac.next[j][k]]) continue;
                dp[i][ac.next[j][k]]=(dp[i][ac.next[j][k]]+dp[i-1][j])%mod;
//状态转移方程是个要点,因为初始化时只有dp[0][0]为1,所以基本也以模拟树上DP
            }
        }
    }
    int res=0;
    for(int i=0;i<=ac.id;i++){
        res=(res+dp[m][i])%mod;
    }
    cout<<(powmod(26,m)-res+mod)%mod<<endl;
}
int main()
{
    scanf("%d%d",&n,&m);
    ac.init();//不知道为啥,大佬们都喜欢把板子写在结构体里
    while(n--){
        scanf("%s",s);
        ac.Insert(s);
    }
    ac.build();
    solve();
}

POJ 2778

原题地址

代码:
由于数据量大了很多(1e9),所以显而易见不能简单DP
离散数学有个非常神奇的知识,即对于一个图的邻接矩阵,这个矩阵的m次方,第i行j列的值代表从i到j路径为m的路径数(左孝凌版离散教材P290)
所以上面的DP过程被转化为矩阵快速幂
代码来自参考博客

//#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define maxn 110
using namespace std;
const int mod=100000;
int nn,mm;
char st[maxn];
struct Marix{//矩阵
    int mo[maxn][maxn],n;
    Marix(){}
    Marix(int _n){
        n=_n;
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++) mo[i][j]=0;
        }
    }
};
Marix mul(Marix a,Marix b){//矩阵乘法
    Marix res=Marix(a.n);
    for(int i=0;i<a.n;i++){
        for(int j=0;j<a.n;j++){
            for(int k=0;k<a.n;k++){
                int tmp=(long long )a.mo[i][k]*b.mo[k][j]%mod;
                res.mo[i][j]=(res.mo[i][j]+tmp)%mod;
            }
        }
    }
    return res;
}
Marix powMod(Marix a,int n){//矩阵快速幂
    Marix nul;
    nul=Marix(a.n);
    for(int i=0;i<nul.n;i++){
        nul.mo[i][i]=1;
    }
    while(n){
        if(n&1) nul=mul(nul,a);
        a=mul(a,a);
        n>>=1;
    }
    return nul;
}
map<char,int>mp;
struct Trie{//AC自动机
    int next[maxn][4],fail[maxn],End[maxn],root,id;
    int newnode(){
        for(int i=0;i<4;i++){
            next[id][i]=-1;
        }
        End[id]=0;
        return id++;
    }
    void init(int nn){
        mp['A']=0,mp['C']=1,mp['T']=2,mp['G']=3;
        id=0;
        root=newnode();
    }
    void Insert(char *str){
        int now=root;
        int len=strlen(str);
        for(int i=0;i<len;i++){
            if(next[now][mp[str[i]]]==-1){
                next[now][mp[str[i]]]=newnode();
            }
            now=next[now][mp[str[i]]];
        }
        End[now]=1;
    }
    void build(){
        queue<int>que;
        for(int i=0;i<4;i++){
            if(next[root][i]==-1){
                next[root][i]=root;
            }
            else{
                fail[next[root][i]]=root;
                que.push(next[root][i]);
            }
        }
        while(!que.empty()){
            int now=que.front();
            que.pop();
            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];
                    que.push(next[now][i]);
                }
            }
            End[now]|=End[fail[now]];
        }
    }
    Marix getMarix(){//构造Trie图中的邻接可达性矩阵
        Marix Mar=Marix(id);
        for(int i=0;i<id;i++){
            for(int j=0;j<4;j++){
                if(End[next[i][j]]) continue;
                Mar.mo[i][next[i][j]]++;
            }
        }
        return Mar;
    }
}ac;
int main()
{
    scanf("%d%d",&nn,&mm);
    ac.init(nn);
    for(int i=0;i<nn;i++){
        scanf("%s",st);
        ac.Insert(st);
    }
    ac.build();//这里的build其实是getfail
    Marix M=ac.getMarix();
    Marix tmp=powMod(M,mm);
    int res=0;
    for(int i=0;i<tmp.n;i++){
        res=(res+tmp.mo[0][i])%mod;
    }
    cout<<res<<endl;
}

理解了原理就很好写,感觉AC自动机已经和网络流一个地位了

HDU 2243

原题地址

代码:
参考博客
思路和上一题大致相同,不过这一题要求询问路径小于m的方案数之和,这里我们可以增加一列,最后一列全为1,这样最后一列的第一行每次的值为前m-1次第一行的值之和+1(路径长为0的情况有一种)。
代码见参考博客

你可能感兴趣的:(业余ACMer笔记·AC自动机)