AC自动机总结

AC自动机总结

AC自动机简述

功能

多模板串对单个或多个串的匹配问题

主体思想

原理同\(kmp\) , 在\(trie\)树上使用变种的\(kmp\)

实现

需要数组 : \(trie[N][26],fail[N]\)

\(fail\)即我们所说的失配函数,\(trie[]\)则略有变更

准确一点得说,\(fail\)函数是不需要知道后继字母的失配

\(trie\)树上的节点经过处理后,就可以直接\(O(1)\)访问后继每一种字母的情况的下一个位置,不需要再一次次失配

预处理
AC自动机的预处理与\(kmp\)的预处理只有一点不同

\(kmp\)的 失配数组\(nxt[N]\) , 是在不知道下一位的字母,一次次失配直到与下一位匹配

int j=0;
for(int i=1;i<=n;++i){
    while(j && s[i]!=t[j+1]) j=nxt[j];
    if(s[i]==t[j+1]) j++;
}

然而AC自动机既然已经基于\(trie\)树结构,自然可以对于每个下一位字母的情况来匹配,这里我们分类讨论

​ 如果已经存在同字母的节点,那么就是下一位节点,而它的\(fail\)就是上一个失配位置\(fail\)的这一个字母的后继节点

​ 如果不存在,就是上一个失配位置\(fail\)的这一个字母的后继节点

(?Are You Kidding Me ?)

事实上是,AC自动机上的\(trie\)树节点,对于不存在的点,我们开出一个虚点作为这个点下一位是这个字母的nxt

而这个过程就可以通过上面分类讨论里描述的方式递推

这样处理下一位的匹配时,就能够直接访问\(trie\)树上所指向的节点

预处理我们通过广搜来递推每个点的\(fail,trie[]\)

struct AC_automation{
    static const int SIZE=N*26;
    int trie[SIZE][26],cnt,fail[SIZE];
    void Build(char s[N][51]){
        rep(i,1,n) Insert(s[i]);
        static queue <int> que;
        rep(i,0,25) if(trie[0][i]) {
            que.push(trie[0][i]);
            fail[trie[0][i]]=0;
        }
        while(!que.empty()) {
            int u=que.front(); que.pop();
            rep(i,0,25) {
                int &v=trie[u][i];
                if(v) {
                    fail[v]=trie[fail[u]][i];
                    que.push(v);
                } else v=trie[fail[u]][i];
            }
        }
    }
}AC;

如果你还不清晰,那没有关系,大不了我们先从背板子做起

关于插叙操作的实现

我们已经知道,每个点的后继状态可以直接通过\(trie\)数组访问

而这里的查询并不只有这么简单

事实上,每一个节点对应的不止是这个节点上所对应的的模版串末尾,

因为模版串直接会含有前后缀的包含关系,所以我们这里要通过一次次的强行失配来访问这个节点对应的所有后缀包含的模版串

写成代码就是

void Que(char *s){
    int p=0,n=strlen(s+1);
    rep(i,1,n) {
        p=trie[p][s[i]-'a'];
        for(int j=p;j;j=fail[j]) {
            ;
            ;//这里该干啥干啥
        }
    }
}

对于最基础的单串访问,我们直接开一个标记记录这个节点是否被加过答案即可

struct AC_automation{
    static const int SIZE=N*26;
    int trie[SIZE][26],End[SIZE],cnt,fail[SIZE],vis[SIZE];
    int Query(char *s) {
        memset(vis,0,sizeof vis);
        int Ans=0;
        int p=0;
        rep(i,0,strlen(s)-1) {
            int x=s[i]-'a';
            p=trie[p][x];
            for(int j=p;j && !vis[j];j=fail[j]) {//注意如果已经访问就可以break了
                Ans+=End[j];
                vis[j]=1;
            }
        }
        return Ans;
    }
}AC;


int main(){ 
    rep(kase,1,rd()) {
        AC.clear();
        n=rd();
        rep(i,1,n) scanf("%s",s[i]);
        AC.Build(s);
        scanf("%s",str);
        printf("%d\n",AC.Query(str));
    }
}

好的现在我们可以愉快得去A掉模板题

话说我还写了一种指针的,由于是第一次写指针,写的比较粗糙,跑得也不快


int n,m,k;

char s[N][51];
char str[M];


struct AC_automation{
    struct Node{
        int End,vis;
        Node *son[26],*fail;
        Node(){ 
            End=0;
            rep(i,0,25) son[i]=NULL;
            fail=NULL;
            vis=0;
        }
    } rt;
    void clear(){ rt=Node(); }
    void Insert(char *s){ 
        Node *p=&rt;
        rep(i,0,strlen(s)-1) {
            int x=s[i]-'a';
            if(p->son[x]==NULL)  p->son[x]=new Node();
            p=p->son[x];
        }
        p->End++;
    }
    void Build(char s[N][51]){
        rep(i,1,n) Insert(s[i]);
        static queue <Node*> que;
        rep(i,0,25) if(rt.son[i]!=NULL) {
            que.push(rt.son[i]);
            rt.son[i]->fail=&rt;
        } else rt.son[i]=&rt;
        while(!que.empty()) {
            Node *u=que.front(); que.pop();
            rep(i,0,25) {
                if(u->son[i]!=NULL) {
                    u->son[i]->fail=u->fail->son[i];
                    que.push(u->son[i]);
                } else u->son[i]=u->fail->son[i];
            }
        }
    }
    int Query(char *s) {
        int Ans=0;
        Node *p=&rt;
        rep(i,0,strlen(s)-1) {
            int x=s[i]-'a';
            p=p->son[x];
            for(Node *j=p;j!=NULL && !j->vis; j=j->fail) {
                Ans+=j->End;
                j->vis=1;
            }
        }
        return Ans;
    }
}AC;

int main(){ 
    rep(kase,1,rd()) {
        AC.clear();
        n=rd();
        rep(i,1,n) scanf("%s",s[i]);
        AC.Build(s);
        scanf("%s",str);
        printf("%d\n",AC.Query(str));
    }
}


//http://acm.hdu.edu.cn/showproblem.php?pid=2222

学完模板的旁友们先不要跑!你们还不会多串匹配呢!

观察到我们的每个点都有一个所指向的\(fail\)节点,令这个点为父亲,我们就能得到一棵有\(fail\)指针构成的树,暂且称其为\(fail\)树吧

所以我们访问每个节点时,其实就是访问到了其在\(fail\)树上所对应的的一段到根的路径上所对应的点

这个东西我们就可以有请各种神仙解法来维护啦(建议自己think think)

练习

温馨提示:AC自动机的练习题可能卡内存

POJ - 1204

把要查询的串都扔进AC自动机,然后暴力check就是了

const int z[10][4]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};

int n,m,q;
char s[N][N];
char str[N][N];

int px[N],py[N],dir[N];


namespace AC{
    static const int SIZE=1e6+10;
    int trie[SIZE][26];
    vector <int> End[SIZE];
    int fail[SIZE],cnt,vis[SIZE];
    int Insert(char *s){ 
        int p=0;
        rep(i,0,strlen(s)-1) {
            int x=s[i]-'A';
            ((!trie[p][x])&&(trie[p][x]=++cnt));
            p=trie[p][x];
        }
        //cout<<"Add"<<p<<endl;;
        return p;
    }
    void Build(char s[N][N]){
        rep(i,1,q) End[Insert(s[i])].push_back((int)i);
        static queue <int> que;
        rep(i,0,25) if(trie[0][i]) que.push(trie[0][i]);
        while(!que.empty()) {
            int u=que.front(); que.pop();
            //cout<<u<<endl;
            rep(i,0,25) {
                int &v=trie[u][i];
                if(v) {
                    fail[v]=trie[fail[u]][i];
                    que.push(v);
                } else v=trie[fail[u]][i];
            }
        }
    }
    void dfs(int x,int y,int d,int p) {
        //cout<<x<<' '<<y<<' '<<d<<' '<<p<<endl;
        p=trie[p][s[x][y]-'A'];
        for(int j=p; j && !vis[j]; j=fail[j]) {
            vis[j]=1;
            rep(k,0,End[j].size()-1) {
                int t=End[j][k];
                px[t]=x,py[t]=y;
                dir[t]=d;
            }
        }
        x+=z[d][0],y+=z[d][1];
        if(x>=n||y>=m||x<0||y<0) return;
        dfs(x,y,d,p);
    }
}
using AC::dfs;



int main(){
    n=rd(),m=rd(),q=rd();
    rep(i,0,n-1) scanf("%s",s[i]);
    rep(i,1,q) scanf("%s",str[i]);
    AC::Build(str);
    rep(i,0,m-1) dfs(n-1,i,0,0);
    rep(i,0,n-1) dfs(i,0,1,0); rep(j,0,m-1) dfs(n-1,j,1,0);
    rep(i,0,n-1) dfs(i,0,2,0);
    rep(i,0,n-1) dfs(i,0,3,0); rep(j,0,m-1) dfs(0,j,3,0);
    rep(i,0,m-1) dfs(0,i,4,0);
    rep(i,0,m-1) dfs(0,i,5,0); rep(i,0,n-1) dfs(i,m-1,5,0);
    rep(i,0,n-1) dfs(i,m-1,6,0);
    rep(i,0,n-1) dfs(i,m-1,7,0); rep(j,0,m-1) dfs(n-1,j,7,0);
    rep(i,1,q) {
        int l=strlen(str[i])-1;
        printf("%d %d %c\n",px[i]-l*z[dir[i]][0],py[i]-l*z[dir[i]][1],'A'+dir[i]);
    }
}

ZOJ - 3228

我写的比较奇怪

先把模板串都丢进去

然后跑插叙

对于允许重叠的,我们直接对\(fail\)树上一段路径的节点的答案++

否则我们分串的长度讨论,对于每种长度的串处理一个答案\(dp[i][6]\)

int n;
char s[N][10],str[N];
int kind[N],End[N];
const int SIZE=N*6;
int dp[SIZE][7],pre[SIZE][7];

namespace AC{
    int trie[SIZE][26];
    int fail[SIZE],cnt;
    void clear(){ 
        cnt=0;
        memset(trie,0,sizeof trie);
    }
    int Insert(char *s){ 
        int p=0;
        rep(i,0,strlen(s)-1) {
            int x=s[i]-'a';
            ((!trie[p][x])&&(trie[p][x]=++cnt));
            p=trie[p][x];
        }
        return p;
    }
    void Build(){
        static queue <int> que;
        rep(i,0,25) if(trie[0][i]) {
            que.push(trie[0][i]);
            fail[trie[0][i]]=0;
        }
        while(!que.empty()) {
            int u=que.front(); que.pop();
            rep(i,0,25) {
                int &v=trie[u][i];
                if(v) {
                    fail[v]=trie[fail[u]][i];
                    que.push(v);
                } else v=trie[fail[u]][i];
            }
        }
    }
    void Que(char *s) {
        memset(dp,0,sizeof dp);
        memset(pre,-63,sizeof pre);
        int p=0;
        rep(i,0,strlen(s)-1) {
            p=trie[p][s[i]-'a'];
            for(reg int j=p; j; j=fail[j]) {
                dp[j][0]++;
                rep(k,1,6) {
                    if(i-pre[j][k]>=k) {
                        dp[j][k]++;
                        pre[j][k]=i;
                    }
                }
            }
        }
    }
}



int main(){
    int kase=0;
    while(~scanf("%s",str)) {
        printf("Case %d\n",++kase);
        AC::clear();
        n=rd();
        rep(i,1,n) {
            kind[i]=rd();
            scanf("%s",s[i]);
            if(kind[i]) kind[i]=strlen(s[i]);
            End[i]=AC::Insert(s[i]);
        }
        AC::Build();
        AC::Que(str);
        rep(i,1,n) printf("%d\n",dp[End[i]][kind[i]]);
        puts("");
    }
}

\[ \ \]

\[ \ \]

HDU - 2457

把AC自动机上的状态存进dp状态里即可

const int N=1e3+10,M=1e7+100,INF=1e9+10;
const int z[10][4]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};

void chk(int &a,int b){ ((a>b)&&(a=b)); }


int n;
char s[N];
int ch[N];


//AC automation
const int SIZE=N;
int trie[SIZE][4];
int End[SIZE];
int fail[SIZE],cnt;
void clear(){
    cnt=0;
    memset(End,0,sizeof End);
    memset(trie,0,sizeof trie);
    memset(fail,0,sizeof fail);
}
void Insert(char *s){ 
    int p=0;
    rep(i,0,strlen(s)-1) {
        int x=ch[(int)s[i]];
        if(!trie[p][x]) trie[p][x]=++cnt;
        p=trie[p][x];
    }
    End[p]=1;
}
void Build() {
    static queue <int> que;
    rep(i,0,3) if(trie[0][i]) que.push(trie[0][i]);
    while(!que.empty()) {
        int u=que.front(); que.pop();
        End[u]|=End[fail[u]];
        rep(i,0,3) {
            int &v=trie[u][i];
            if(v) {
                que.push(v);
                fail[v]=trie[fail[u]][i];
            } else v=trie[fail[u]][i];
        }
    }
}

int dp[N][N];
int kase;
int main(){
    ch[(int)'A']=0,ch[(int)'T']=1,ch[(int)'C']=2,ch[(int)'G']=3;
    while(~scanf("%d",&n) && n) {
        clear();
        rep(i,1,n) scanf("%s",s),Insert(s);
        Build();
        scanf("%s",s+1);
        int m=strlen(s+1);
        memset(dp,63,sizeof dp);
        dp[0][0]=0;
        rep(i,1,m) {
            rep(j,0,cnt) if(!End[j] && dp[i-1][j]<INF) {
                int t=ch[(int)s[i]];
                rep(k,0,3) chk(dp[i][trie[j][k]],dp[i-1][j]+(k!=t));
            }
        }
        int ans=INF;
        rep(j,0,cnt) if(!End[j]) chk(ans,dp[m][j]);
        printf("Case %d: %d\n",++kase,ans<INF?ans:-1);
    }
}

\[ \ \]

\[ \ \]

POJ - 2778

再套一个矩阵就好了



int n,m;
int a[N];
char s[N];
int val[N];

//AC automation
const int SIZE=101;
int trie[SIZE][4];
int End[SIZE];
int fail[SIZE],cnt;

int ch[N];
int Insert(char *s){ 
    int p=0;
    int l=0;
    while(s[l]!='\0') l++;
    rep(i,0,l-1) {
        int x=ch[(int)s[i]];
        //cout<<"insert"<<s<<' '<<i<<' '<<x<<endl;
        if(!trie[p][x]) trie[p][x]=++cnt;
        p=trie[p][x];
    }
    //cout<<p<<endl;
    return p;
}

void Build() {
    static queue <int> que;
    rep(i,0,3) if(trie[0][i]) que.push(trie[0][i]);
    while(!que.empty()) {
        int u=que.front(); que.pop();
        End[u]|=End[fail[u]];
        rep(i,0,3) {
            int &v=trie[u][i];
            if(v) {
                que.push(v);
                fail[v]=trie[fail[u]][i];
            } else v=trie[fail[u]][i];
        }
    }
}

struct Mat{
    int a[SIZE][SIZE];
    void init(){ memset(a,0,sizeof a); }
    void Get1(){ rep(i,0,cnt) a[i][i]=1; }
    Mat operator * (const Mat x) const{
        Mat res; res.init();
        rep(i,0,cnt) rep(j,0,cnt) rep(o,0,cnt) res.a[i][o]=(res.a[i][o]+1ll*a[i][j]*x.a[j][o])%P;
        return res;
    }
}x,res;

int f[1][SIZE],ans[1][SIZE];


int main(){
    ch[(int)'A']=0,ch[(int)'T']=1,ch[(int)'C']=2,ch[(int)'G']=3;
    m=rd(),n=rd();
    rep(i,1,m) {
        scanf("%s",s);
        End[Insert(s)]=1;
    }
    Build();
    //puts("!");
    f[0][0]=1;
    rep(i,0,cnt) if(!End[i]) {
        rep(j,0,3) {
            int nxt=trie[i][j];
            if(End[nxt]) continue;
            x.a[i][nxt]++;
        }
    }
    res.Get1();
    //puts("!");
    while(n) {
        if(n&1) res=res*x;
        x=x*x;
        n>>=1;
    }
    rep(i,0,0) rep(j,0,cnt) rep(o,0,cnt) ans[i][o]=(ans[i][o]+1ll*f[i][j]*res.a[j][o])%P;
    int Ans=0;
    rep(i,0,cnt) (Ans+=ans[0][i])%=P;
    printf("%d\n",Ans);
}



\[ \ \]

\[ \ \]

HYSBZ - 3172

嗯,题目中的文章是由所有单词拼出来的,但是单词直接互相独立

这道题我们才第一次用到一点\(fail\)

首先单词都丢进AC自动机,然后一个个跑匹配

每一次更新\(fail\)树上一段路径前缀的节点即可

事实上就是在哪里放一个1,按次向根累加就能得到答案

int n;
int l;
string s[N];
int End[N];

const int SIZE=N;
int trie[N][26],fail[N],cnt;
int Insert(string &s){
    int p=0;
    rep(i,0,s.size()-1) {
        int x=s[i]-'a';
        if(!trie[p][x]) trie[p][x]=++cnt;
        p=trie[p][x];
    }
    return p;
}

int line[SIZE],Ans[SIZE],lc;
void Build(){
    static queue <int> que;
    rep(i,0,25) if(trie[0][i]) que.push(trie[0][i]);
    while(!que.empty()){
        int u=que.front(); que.pop();
        line[++lc]=u;
        rep(i,0,25) {
            int &v=trie[u][i];
            if(v) {
                que.push(v);
                fail[v]=trie[fail[u]][i];
            } else v=trie[fail[u]][i];
        }
    }
}





int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    rep(i,1,n) {
        cin>>s[i];
        End[i]=Insert(s[i]);
    }
    Build();
    rep(i,1,n){
        int p=0;
        rep(j,0,s[i].size()-1) {
            p=trie[p][s[i][j]-'a'];
            Ans[p]++;
        }
    }
    drep(i,lc,1) Ans[fail[line[i]]]+=Ans[line[i]];//利用广搜序累和
    rep(i,1,n) printf("%d\n",Ans[End[i]]);
}

\[ \ \]

\[ \ \]

HDU - 5069

求后缀与前缀的最大匹配长度

这题我写的很暴力

首先对于前一个字符串,直接匹配到末尾,其实接下来就是求\(fail\)树上的这段前缀上的每一个点与后一个串在\(trie\)树上的位置的最长公共前缀长度

我的做法是:将\(trie\)树树剖,依次访问\(fail\)树上的每一个节点回答询问

访问\(fail\)树上的节点前缀时就可以直接通过每经过一个点就++

对于这一段前缀的一个询问串x,令其在\(trie\)树上的末尾节点为y,我们就是找到所有++的节点最深的在\(y\)对应的\(trie\)树前缀上的位置,这里我直接树剖加二分实现了

int n,m;
string s[N];

const int SIZE=N;
int End[N];
int trie[N][26],fail[N],cnt;
void clear(){
    memset(trie,0,sizeof trie);
    cnt=0;
}
int Insert(string &s){
    int p=0;
    rep(i,0,s.size()-1) {
        int x=s[i]-'A';
        if(!trie[p][x]) trie[p][x]=++cnt;
        p=trie[p][x];
    }
    return p;
}



struct Graph{
    struct Edge{
        int to,nxt;
    }e[SIZE];
    int head[N],ecnt;
    void AddEdge(int u,int v){
        //cout<<"Edge ont the fail "<<u<<' '<<v<<endl;
        e[++ecnt].to=v;e[ecnt].nxt=head[u];
        head[u]=ecnt;
    }
    void clear(){
        memset(head,0,sizeof head);
        ecnt=0;
    }
}G;

int dep[N];
int sz[N],son[N],top[N],fa[N];

void dfs1(int u){
    //cout<<"dfs on the trie"<<u<<endl;
    sz[u]=1,son[u]=-1;
    rep(i,0,25) {
        int v=trie[u][i];
        if(!v) continue;
        dep[v]=dep[u]+1;
        fa[v]=u;
        //cout<<"Edge on the trie "<<u<<' '<<v<<endl;
        dfs1(v);
        sz[u]+=sz[v];
        if(son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;
    }
}

int L[N],R[N],dfn,id[N];
void dfs2(int u,int t){
    //cout<<"dfs on the trie"<<u<<endl;
    top[u]=t;
    id[L[u]=++dfn]=u;
    if(~son[u]) dfs2(son[u],t);
    rep(i,0,25) {
        int v=trie[u][i];
        if(!v||v==son[u]) continue;
        dfs2(v,v);
    }
    R[u]=dfn;
}






void Build(){
    static queue <int> que;
    dfn=0;G.clear();
    fa[0]=-1,dfs1(0);dfs2(0,0);
    rep(i,0,25) if(trie[0][i]) {
        que.push(trie[0][i]);
        fail[trie[0][i]]=0;
    }
    while(!que.empty()){
        int u=que.front(); que.pop();
        //cout<<"#"<<u<<endl;
        G.AddEdge(fail[u],u);
        rep(i,0,25) {
            int &v=trie[u][i];
            if(v) {
                //cout<<v<<endl;
                que.push(v);
                fail[v]=trie[fail[u]][i];
            } else v=trie[fail[u]][i];
        }
    }
}


vector <pair<int,int> > Q[N];

int sum[N];
void Add(int p,int x){
    //cout<<"Add "<<p<<' '<<x<<' '<<endl;
    while(p<=dfn) sum[p]+=x,p+=p&-p;
}
int Que(int p){
    int res=0;
    while(p) res+=sum[p],p-=p&-p;
    return res;
}


int Ans[N];
void dfs_getans(int u){
    Add(L[u],1);
    rep(i,0,Q[u].size()-1) {
        int x=Q[u][i].first,qid=Q[u][i].second;
        while(~x) {
            int t=Que(L[x])-Que(L[top[x]]-1);
            if(t) {//找到一个++的节点
                int p=0;
                t+=Que(L[top[x]]-1);
                drep(j,16,0) if( (p+(1<<j)<L[x]) && sum[p+(1<<j)]<t) t-=sum[p+=(1<<j)];
                p++;
                Ans[qid]=dep[id[p]];
                break;//二分这个节点的位置
            }
            x=fa[top[x]];
        }
    }
    vector <pair<int,int> > tmp;
    swap(tmp,Q[u]);
    for(int i=G.head[u];i;i=G.e[i].nxt) {
        int v=G.e[i].to;
        dfs_getans(v);
    }
    Add(L[u],-1);
}

bool ed;

int main(){
    ios::sync_with_stdio(false);
    while(cin>>n>>m) {
        clear();
        rep(i,1,n) {
            cin>>s[i];
            End[i]=Insert(s[i]);
        }
        Build();
        rep(i,1,m) {
            int x=rd(),y=rd();
            x=End[x],y=End[y];
            Q[x].push_back(make_pair(y,i));
        }
        dfs_getans(0);
        rep(i,1,m) printf("%d\n",Ans[i]);
    }
}


\[ \ \]

\[ \ \]

HDU - 4117

这个就是dp从每一个的子串上转移过来

转移过程中不断匹配,然后统计\(fail\)树上前缀的最大值,这时一个动态的过程

于是我们树剖线段树


const int N=2e4+10,SIZE=2.3e5+10;

bool be;
int n,m;
string s[N];

int End[N],val[N];
int trie[SIZE][26],fail[SIZE],cnt;
void clear(){
    memset(trie,0,sizeof (int) * (cnt+1)*26);
    cnt=0;
}

int Insert(string &s){
    int p=0;
    rep(i,0,s.size()-1) {
        //if(!isalpha(s[i])) while(1);
        int x=s[i]-'a';
        if(!trie[p][x]) trie[p][x]=++cnt;
        p=trie[p][x];
    }
    return p;
}

int sz[SIZE],son[SIZE],top[SIZE],fa[SIZE];

struct Edge{
    int to,nxt;
}e[SIZE];
int head[SIZE],ecnt;
void AddEdge(int u,int v){
    e[++ecnt].to=v;
    e[ecnt].nxt=head[u];
    head[u]=ecnt;
}




void dfs1(int u){
    sz[u]=1,son[u]=-1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!v) continue;
        fa[v]=u;
        dfs1(v);
        sz[u]+=sz[v];
        if(son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;
    }
}

int L[SIZE],R[SIZE],dfn;
//id[SIZE];
void dfs2(int u,int t){
    top[u]=t;
    L[u]=++dfn;
    if(~son[u]) dfs2(son[u],t);
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!v||v==son[u]) continue;
        dfs2(v,v);
    }
    R[u]=dfn;
}

void chk(int &a,int b){ ((a<b)&&(a=b)); }
struct SGT{
    int sum[SIZE<<2];
    void clear() { memset(sum,0,sizeof sum); }
    void Upd(int p,int l,int r,int x,int y){
        chk(sum[p],y);
        if(l==r) return;
        int mid=(l+r)>>1;
        if(x<=mid) Upd(p<<1,l,mid,x,y);
        else Upd(p<<1|1,mid+1,r,x,y);
    }
    int Que(int p,int l,int r,int ql,int qr){
        if(l==ql&&r==qr) return sum[p];
        int mid=(l+r)>>1;
        if(qr<=mid) return Que(p<<1,l,mid,ql,qr);
        else if(ql>mid) return Que(p<<1|1,mid+1,r,ql,qr);
        else return max(Que(p<<1,l,mid,ql,mid),Que(p<<1|1,mid+1,r,mid+1,qr));
    }
}tr;


void Build(){
    static queue <int> que;
    dfn=0;
    memset(head,0,sizeof head);ecnt=0;
    rep(i,0,25) if(trie[0][i]) {
        que.push(trie[0][i]);
        fail[trie[0][i]]=0;
    }
    while(!que.empty()){
        int u=que.front(); que.pop();
        AddEdge(fail[u],u);
        rep(i,0,25) {
            int &v=trie[u][i];
            if(v) {
                que.push(v);
                fail[v]=trie[fail[u]][i];
            } else v=trie[fail[u]][i];
        }
    }
    fa[0]=-1,dfs1(0);dfs2(0,0);
}


bool ed;

int main(){
    //cout<<&ed-&be<<endl;
    ios::sync_with_stdio(false);
    int T; cin>>T;
    rep(kase,1,T) {
        clear();
        tr.clear();
        cin>>n;
        rep(i,1,n) {
            cin>>s[i]>>val[i];
            End[i]=Insert(s[i]);
        }
        Build();
        int ans=0;
        rep(i,1,n) {
            int res=0,p=0;
            rep(j,0,s[i].size()-1) {
                p=trie[p][s[i][j]-'a'];
                int x=p;
                while(~x) {
                    chk(res,tr.Que(1,1,dfn,L[top[x]],L[x]));
                    x=fa[top[x]];
                }
            }
            chk(res,res+val[i]);
            tr.Upd(1,1,dfn,L[End[i]],res);
            chk(ans,res);
            string t;swap(t,s[i]);
        }
        printf("Case #%d: %d\n",kase,ans);
    }
}




HDU - 6096

为什么我觉得这个题是最难的

我不晓得官方做法,于是参考一笑网上神仙的思想

把要匹配的串换过来中间加上一个奇怪的字符再相连

原字符串也复制一份,中间加上一个奇怪的字符,然后就可以直接求匹配了!

由于不能重复,所以会受到长度的限制,用树状数组统计



const int N=1e5+10,K=20,SIZE=1.5e6;
const char d='`';

int n,m;
string s[N],a,b;
int trie[SIZE][27],cnt,len[N],fail[SIZE];
vector <int> vec[SIZE],Q[SIZE];
int End[N];

struct Edge{
    int to,nxt;
}e[SIZE];
int head[SIZE],ecnt;
void AddEdge(int u,int v){
    e[++ecnt]=(Edge){v,head[u]};
    head[u]=ecnt;
}



void clear(){
    memset(trie,0,sizeof (int) * (cnt+1) *27 );
    memset(head,0,sizeof (int) * (cnt+1));
    rep(i,0,cnt) vec[i].clear(),Q[i].clear();
    cnt=0;
}


int Insert(string &s){
    int p=0;
    rep(i,0,s.size()-1) {
        int x=s[i]-d;
        if(!trie[p][x]) trie[p][x]=++cnt;
        p=trie[p][x];
    }
    return p;
}

void Build(){
    static queue <int> que;
    rep(i,0,26) if(trie[0][i]) {
        que.push(trie[0][i]);
        fail[trie[0][i]]=0;
    }
    while(!que.empty()) {
        int u=que.front(); que.pop();
        AddEdge(fail[u],u);
        //cout<<u<<endl;
        rep(i,0,26) {
            int &v=trie[u][i];
            if(v) {
                fail[v]=trie[fail[u]][i];
                que.push(v);
            } else v=trie[fail[u]][i];
        }
    }
}

int Ans[N];
int sum[N];
void Add(int p,int x){
    while(p) sum[p]+=x,p-=p&-p;
}
int Que(int p){
    int res=0;
    while(p<N) res+=sum[p],p+=p&-p;
    return res;
}

void dfs(int u) {
    //cout<<"#"<<u<<endl;
    rep(i,0,Q[u].size()-1) Ans[Q[u][i]]=-Que(len[Q[u][i]]*2-1);
    rep(i,0,vec[u].size()-1) {
        Add(vec[u][i],1);
        //cout<<"Add "<<vec[u][i]<<endl;
    }
    for(int i=head[u];i;i=e[i].nxt) {
        int v=e[i].to;
        dfs(v);
    }
    rep(i,0,Q[u].size()-1) Ans[Q[u][i]]+=Que(len[Q[u][i]]*2-1);
}





int main(){
    ios::sync_with_stdio(false);
    int T;cin>>T;
    rep(kase,1,T) {
        cin>>n>>m;
        clear();
        rep(i,1,n) {
            cin>>s[i];
            s[i]=s[i]+d+s[i];
        }
        rep(i,1,m) {
            cin>>a>>b;
            b=b+d+a;
            len[i]=b.size();
            Q[Insert(b)].push_back((int)i);
        }
        Build();
        rep(i,1,n) {
            int p=0,len=s[i].size();
            rep(j,0,len-1) {
                p=trie[p][s[i][j]-d];
                //cout<<"to pos "<<p<<endl;
                if(p) vec[p].push_back(len);
            }
        }
        memset(sum,0,sizeof sum);
        dfs(0);
        rep(i,1,m) printf("%d\n",Ans[i]);
    }
}





转载于:https://www.cnblogs.com/chasedeath/p/11530544.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python AC自动机是一个用于字符串匹配的算法,它可以高效地在一段文本中查找多个预定义的模式。它的实现可以使用多种库,其中包括ac自动机python和ahocorasick-python。 ac自动机python是一个对标准的ac自动机算法进行了完善和优化的实现,适用于主流的Python发行版,包括Python2和Python3。它提供了更准确的结果,并且可以通过pip进行安装,具体的安装方法可以参考官方文档或者使用pip install命令进行安装。 ahocorasick-python是另一个实现AC自动机的库,它也可以用于Python2和Python3。你可以通过官方网站或者GitHub源码获取更多关于该库的信息和安装指南。 对于AC自动机的使用,一个常见的例子是在一段包含m个字符的文章中查找n个单词出现的次数。要了解AC自动机,需要有关于模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机的算法包括三个步骤:构造一棵Trie树,构造失败指针和模式匹配过程。在构造好AC自动机后,可以使用它来快速地在文本中查找预定义的模式,并统计它们的出现次数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ahocorasick-python:AC自动机python的实现,并进行了优化。 主要修复了 查询不准确的问题](https://download.csdn.net/download/weixin_42122986/18825869)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python实现多模匹配——AC自动机](https://blog.csdn.net/zichen_ziqi/article/details/104246446)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值