1. 程式人生 > >[CF587F]Duff is Mad[AC自動機+根號分治+分塊]

[CF587F]Duff is Mad[AC自動機+根號分治+分塊]

題意

給你 \(n\) 個串 \(s_{1\cdots n}\) ,每次詢問給出 \(l,r,k\) ,問在 \(s_{l\cdots r}\) 中出現了多少次 \(s_k\)

\(n,q,\sum|s|\le 10^5\)

分析

  • 先建AC自動機的 \(fail\) 樹, 我們考慮兩種暴力:

    • \(l​\)\(r​\) 中的每個串的末尾節點子樹標記,查詢 \(s_k​\) 的所有節點 \(fail​\) 樹到根的路徑和。
    • \(s_k\) 的每個節點的子樹標記,查詢 \(l\)\(r\) 中的每個末尾節點的點權和。
  • 發現這兩種做法在不同的資料下有著不同的效果,考慮根號分治:

    • 如果 \(|s_k|\le\sqrt n\) 採用第一種方式,差分查詢,這樣操作每個串的次數不超過 \(\sqrt n\) ,動態維護字首和。
    • 如果 \(|s_k|>\sqrt n\) 採用第二種方式,記錄字首和即可,這樣的串不超過 \(\sqrt n\) 個。
  • 我們發現,對於 \(dfs\) 序陣列來說,修改次數是 \(O(n)\) 級別,但是查詢次數卻是 \(O(n\sqrt n)\) 級別的,能不能平衡兩種操作時間複雜度呢?

    考慮分塊來維護字首和,每個塊維護一個加標記。這樣修改變成了 \(O(\sqrt n)\) ,但是查詢變成了 \(O(1)\)

  • 總時間複雜度為 \(O(n\sqrt n)\)

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define go(u) for(int i = head[u], v = e[i].to; i; i=e[i].lst, v=e[i].to)
#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define pb push_back
#define re(x) memset(x, 0, sizeof x)
inline int gi() {
    int x = 0,f = 1;
    char ch = getchar();
    while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar();}
    while(isdigit(ch)) { x = (x << 3) + (x << 1) + ch - 48; ch = getchar();}
    return x * f;
}
template <typename T> inline void Max(T &a, T b){if(a < b) a = b;}
template <typename T> inline void Min(T &a, T b){if(a > b) a = b;}
const int N = 1e5 + 7;
int n, q, sz = 317, tim;
int L[N], R[N], in[N], out[N], ch[N][26];
char s[N];
namespace tr{
    int edc;
    int head[N];
    struct edge {
        int lst, to;
        edge(){}edge(int lst, int to):lst(lst), to(to) {}
    }e[N];
    void Add(int a, int b) {
        e[++edc] = edge(head[a], b), head[a] = edc;
    }
    void dfs(int u) {
        in[u] = ++tim;  go(u) dfs(v); out[u] = tim;
    }
}
namespace ac {
    int endp[N], fail[N], ndc;
    int idx(char c) { return c - 'a';}
    void ins(int a) {
        L[a] = R[a - 1] + 1;
        scanf("%s", s + L[a]);
        R[a] = L[a] + strlen(s + L[a]) - 1;
        int u = 0;
        for(int i = L[a]; i <= R[a]; ++i) {
            int c = idx(s[i]);
            if(!ch[u][c]) ch[u][c] = ++ndc;
            u = ch[u][c];
        }
        endp[a] = u;
    }
    void getfail() {
        queue<int>Q;
        for(int c = 0; c < 26; ++c) if(ch[0][c]) Q.push(ch[0][c]), tr::Add(0, ch[0][c]);
        while(!Q.empty()) {
            int u = Q.front();Q.pop();
            for(int c = 0; c < 26; ++c) {
                int &v = ch[u][c];
                if(!v) { v = ch[fail[u]][c]; continue;}
                fail[v] = ch[fail[u]][c];
                Q.push(v);
                tr::Add(fail[v], v);
            }
        }
    }
}
struct data {
    int l, r, k, id, opt;
    bool operator <(const data &rhs) const {
        return r < rhs.r;
    }
}t[N << 1];
vector<data>G[N];
int qc, bl[N];//時間戳陣列長度為tim
int Rp(int x){ return min(tim, x * sz);}
LL ans[N], sum[N], pre[N], add[400];
void mdf(int p, int v) {
    if(p == tim + 1) return;
    for(int i = p; i <= Rp(bl[p]); ++i) pre[i] +=v;
    for(int i = bl[p] + 1; i <= bl[tim]; ++i) add[i] += v;
}
LL qry(int p) { return pre[p] + add[bl[p]]; }
void modify(int l, int r) { mdf(l, 1);  mdf(r + 1, -1);}
LL query(int l, int r) { return qry(r) - qry(l - 1); }
void solve(int x) {
    re(sum), re(pre), re(add);
    int u = 0;
    for(int i = L[x]; i <= R[x]; ++i) {
        u = ch[u][ac::idx(s[i])];
        mdf(in[u], 1);
    }
    for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + query(in[ac::endp[i]], out[ac::endp[i]]);
    for(auto v: G[x]) {
        ans[v.id] = sum[v.r] - sum[v.l - 1];
    }
}
void Addstring(int x) {
    int u = ac::endp[x];
    modify(in[u], out[u]);
}
LL Substring(int x) {
    int u = 0;LL ans = 0;
    for(int i = L[x]; i <= R[x]; ++i) {
        u = ch[u][ac::idx(s[i])];
        ans += qry(in[u]);
    }
    return ans;
}
int main() {
    n = gi(), q = gi();
    rep(i, 1, n) ac::ins(i);
    ac::getfail(), tr::dfs(0);
    rep(i, 1, tim) bl[i] = (i - 1) / sz + 1;
    
    rep(i, 1, q) {
        int l = gi(), r = gi(), k = gi();
        if(R[k] - L[k] + 1 <= sz) {
            t[++qc] = (data){ 0, l - 1, k, i, -1 };
            t[++qc] = (data){ 0, r, k, i, 1 };
        }else
            G[k].pb((data){ l, r, k, i, 1});
    }
    rep(i, 1, n) if(R[i] - L[i] + 1 > sz) solve(i);
    
    re(pre), re(add);
    sort(t + 1, t + 1 + qc);
    for(int i = 0, j = 1; i <= n; ++i) {
        if(i) Addstring(i);
        for(; j <= qc && t[j].r == i; ++j) {
            ans[t[j].id] += 1ll * t[j].opt * Substring(t[j].k);
        }
    }
    rep(i, 1, q) printf("%lld\n", ans[i]);
    return 0;
}