牛客競賽——string(AC自動機+離線處理)

學習來源:https://oi-wiki.org/string/ac-automaton/  and 算法競賽入門經典訓練指南

題目鏈接:https://ac.nowcoder.com/acm/problem/14612 

題目參考代碼思路: https://www.cnblogs.com/letlifestop/p/10869285.html

題目大意:首先是T組測試樣例,然後先輸入n個字符串放在倉庫中,然後m次詢問,每一次詢問有兩種類型。類型1是在倉庫中新添加一個字符串。類型2是輸入一個字符串,然後問當前倉庫中有多少字符串是你操作2輸入的字符串的子串 

具體思路:一開始是按照在輸入新的n個字符串之後建立fail樹的,然後每次查詢的時候再重新建立一顆fail樹(因爲考慮到會添加許多新的字符串),然後就T了。既然詢問次數達到了1e5.我們就可以考慮在線的強制轉換爲離線的算法。(多謝旭偉的提醒)

我們先將所有的字符串輸入進來,然後再去建fail樹。對於每次操作2,我們從輸入的順序倒着來。在初始輸入階段中,當爲操作1時,我們保存當前字符串在trie樹上的結尾下標。當全部輸入完的

時候,倒序查詢,每查詢完一個,就將這個操作下面添加的字符串操作在trie樹上的權值下標取消就好了。

#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
#include <map>
#include <queue>
#include <cstdio>
#include <string>
#include <stack>
#include <set>
using namespace std;
typedef long long ll;
const int N=3e6+6;
int n,m;
//離線處理結構體
struct node{
    int type;
    string s;
    int pos;
}p[100010];
//AC自動機
namespace ac{
    int tr[N][26],tot;
    int val[N],fail[N],last[N];
    //初始化
    void init(){
        for(int i=0;i<=tot;i++){
            fail[i]=0;val[i]=0;last[i]=0;
            for(int j=0;j<26;j++){
                tr[i][j]=0;
            }
        }
        tot=0;
    }
    //建立tire樹
    int insert(string s){
        int u=0;
        for(int i=0;s[i];i++){
            if(!tr[u][s[i]-'a'])tr[u][s[i]-'a']=++tot;
            u=tr[u][s[i]-'a'];
        }
        val[u]++;
        return u;//返回該字符串的節點下標
    }
    //建立字典圖,也就是getfail
    void build(){
        queue<int >q;
        //初始化隊列
        for(int i=0;i<26;i++){
            if(tr[0][i]){
                fail[tr[0][i]]=0;q.push(tr[0][i]);last[tr[0][i]]=0;
            }
        }
        //按bfs序列計算失配函數fail和後綴鏈接last
        while(!q.empty()){
            int u=q.front();
            q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])
                fail[tr[u][i]]=tr[fail[u]][i],q.push(tr[u][i]);
                else {tr[u][i]=tr[fail[u]][i];continue;}
                last[tr[u][i]]=val[fail[tr[u][i]]]?fail[tr[u][i]]:last[fail[tr[u][i]]];
            }
        }
    }
    //計算以節點t結尾的所有字符串(有着相同後綴的)個數和
    int cal(int t){
        int res=0;
        while(t){
            res+=val[t];
            t=last[t];
        }
        return res;
    }
    //模式匹配
    int query(string t){
        int u=0,res=0;
        for(int i=0;t[i];i++){
            int pt=t[i]-'a';
            while(u&&!tr[u][pt])u=fail[u];//沿着失配邊走,知道可以匹配
            u=tr[u][pt];
            if(val[u]){//是模式串的結尾節點
                res+=cal(u);
            }
            else if(last[u]){//不是模式串的結尾節點就用last[u]找到u的結尾節點(有相同後綴)
                res+=cal(last[u]);
            }
        }
        return res;
    }
}
string s;
int ans[100010];
int main()
{
    int t;
    cin>>t;
    //scanf("%d",&t);
    while(t--){
        ac::init();
        cin>>n>>m;
        //scanf("%d %d",&n,&m);
        for(int i=0;i<n;i++){
            cin>>s;
            //scanf("%s",s);
            ac::insert(s);
        }
       // ac::build();
        int num=0;
        int type;
        while(m--){
            cin>>type>>s;
            if(type==1){
                p[++num].type=type;
                p[num].pos=ac::insert(s);//記錄下該字符串的節點下標,以備後續抹掉
            }
            else {
                p[++num].type=2;
                p[num].s=s;
            }
        }
        ac::build();//建圖:得到失配函數
        //倒敘查詢
        for(int i=num;i>0;i--){
            if(p[i].type==2){
               ans[i]=ac::query(p[i].s);//模式匹配
            }
            else {
                ac::val[p[i].pos]--;//抹掉該字符串(val是個很重要的值)
            }
        }
        for(int i=1;i<=num;i++){
            if(p[i].type==2)cout<<ans[i]<<endl;
        }
    }
    getchar();
    getchar();
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章