BZOJ 2434 [NOI 2011] 阿狸的打字機

更好的閱讀體驗 Press Here

AC自動機 可食用最佳練手題

題意

有三種操作
* 在字符串的末尾加入小寫字母
* 刪除字符串末尾的字符
* 將當前字符串輸出(不刪除)

問第 x 個輸出的字符串 在第 y 個輸出的字符串中出現了幾次

Solution

當然直接暴力是沒有問題的
存下第 x 個輸出的字符串 然後用 KMP 優化匹配
時間複雜度可以達到 O(ML) ,仍然會超時

既然需要對多個字符串進行匹配,自然的我們想到 AC自動機 (後綴自動機的泥奏凱)

發現如果第 x 個輸出的字符串在第 y 個字符串的 n 位置匹配,那麼第 x 個輸出的字符串一定是第 y 個字符串的 n 位置的後綴,即可以通過 fail 指針轉移到
那麼原題就轉換爲了在第 y 個字符串中有多少個字符能夠通過 fail 轉移到第 x 個字符串的結尾位置

但是直接做時間複雜度仍然很高
我們需要枚舉每個 y 串,維護一個計數器,從根一路遍歷到 y 串的末尾節點,途中對於每個節點,如果其 fail 指針指向的是某 x 串的末尾節點,則累加
這個時間複雜度還是 O(ML) ,無法通過此題

看到多個點通過 fail 上找一個點,爲何不將其轉化爲一個點通過 fail 找其他點呢?
那麼將 fail 反向,問題變爲 x 串的結尾位置能夠通過反向的 fail 到達多少屬於 y 的節點

考慮進一步優化,再這樣一棵由反向 fail 組成的樹上,每個節點所能到達的點一定在它的子樹之中,可以使用 DFS 序維護,同時用樹狀數組求答案

如何統計?
只需要重新按照原來輸入再走一遍,每次遇到加入某個節點,則加入當前節點的 DFS 序;每次刪除某個節點,則刪除當前節點的 DFS 序;每次輸出某個串,說明現在樹狀數組維護的就是這個串所有節點的 DFS 序,那麼就可以統計這個串作爲 y 串的答案了

詳細代碼如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int m , tot , l[N] , r[N] , t[N] , ans[N];
char s[N];
vector <int> e[N];
vector <pair<int , int>> q[N];
void add(int , int);
int query(int);
struct Aho {
    int ch[N][26] , fail[N] , fa[N] , id[N] , go[N];
    int cnt , total;
    void build() {
        int n = strlen(s) , now = 0;
        for(int i = 0 ; i < n ; ++ i) {
            if(s[i] == 'B') now = fa[now];
            else if(s[i] == 'P') {id[++ total] = now; go[now] = total;}
            else {
                if(!ch[now][s[i] - 'a']) {
                    ch[now][s[i] - 'a'] = ++ cnt;
                    fa[cnt] = now;
                }
                now = ch[now][s[i] - 'a'];
            }
        }

        queue <int> q;
        for(int i = 0 ; i < 26 ; ++ i)
            if(ch[0][i]) {
                q.push(ch[0][i]);
                e[0].push_back(ch[0][i]);
            }
        while(!q.empty()) {
            int x = q.front(); q.pop();
            for(int i = 0 ; i < 26 ; ++ i) {
                if(ch[x][i]) {
                    q.push(ch[x][i]);
                    fail[ch[x][i]] = ch[fail[x]][i];
                    e[ch[fail[x]][i]].push_back(ch[x][i]);
                }
                else ch[x][i] = ch[fail[x]][i];
            }
        }
    }
    void get_ans() {
        int n = strlen(s) , now = 0;
        for(int i = 0 ; i < n ; ++ i) {
            if(s[i] == 'B') {add(l[now] , -1); now = fa[now];}
            else if(s[i] == 'P') {
                for(auto j = q[go[now]].begin() ; j != q[go[now]].end() ; ++ j)
                    ans[j -> second] = query(r[id[j -> first]]) - query(l[id[j -> first]] - 1);
            }
            else {
                now = ch[now][s[i] - 'a'];
                add(l[now] , 1);
            }
        }
    }
}ac;
int read() {
    int ans = 0 , flag = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') flag = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {ans = ans * 10 + ch - '0'; ch = getchar();}
    return ans * flag;
}
int lowbit(int x) {return x & (-x);}
void add(int x , int y) {if(x) for(int i = x ; i <= tot ; i += lowbit(i)) t[i] += y;}
int query(int x) {int ans = 0; if(x) for(int i = x ; i ; i -= lowbit(i)) ans += t[i]; return ans;}

void dfs(int x) {
    l[x] = ++ tot;
    for(auto i = e[x].begin() ; i != e[x].end() ; ++ i) dfs(*i);
    r[x] = tot;
}
void init() {
    scanf("%s" , s);
    m = read();
    for(int i = 0 ; i < m ; ++ i) {
        int x = read() , y = read();
        q[y].push_back({x , i});
    }
}
void work() {
    ac.build();
    dfs(0);
}
void print() {for(int i = 0 ; i < m ; ++ i) printf("%d\n" , ans[i]);}
int main() {
    init();
    work();
    ac.get_ans();
    print();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章