对一台打字机,有两个功能字符:P 表示打印当前缓存中的串,B 表示删去缓存末尾的一个字符。给定一个操作串。你需要回答 \(m\) 个询问,每个询问指定 \((x,y)\),求第 \(x\) 个打印的字符串在第 \(y\) 个打印的字符串中出现了多少次。

Solution

\(S\) 是否在 \(T\) 出现,就是问 ACAM 上,\(T\) 到 Trie 根的沿着 Trans 边路径上,有没有 \(S\) 在 fail 树上的子树内的结点

先处理操作串来建 Trie,遇到 P 操作就将当前结点打标记,遇到 B 操作就退回到 Trie 上的父亲结点

对于询问 \((x,y)\),就是要求 fail 树上以 \(x\) 为根的子树内,有多少个 \(y\) 沿着 Trie 树上的边走到根的路径上的结点

注意这里的 Trie 树和 fail 树上的边要加以区分,这就是为什么不能直接套个链剖走人

考虑离线,将 Trie 树 DFS 一遍,同时维护一个桶,桶中下标为 \(i\) 的位置表示当前所在点到根的链上是否有 \(i\) 这个点

这个维护方式是显然的,每次 DFS 进入一个点时就 \(+1\),完成一个点时就 \(-1\)

我们将每个询问挂在 \(y\) 上,那么进入 \(y\) 后,询问 \(x\) 这个点的子树和即可

于是桶需要按照 DFS 序做下标,需要用树状数组维护

(注意区分这里所涉及的 Trans 图边,Fail 树边,Trie 树边)

#include <bits/stdc++.h>
using namespace std;

const int N = 1000005;
const int dbg = 0;

int ar[N]; // index: 1 ~ N
int lowbit(int t) { return t & (-t); }
void add(int i, int v) {
    if(i==0) return;
    for (; i < N; ar[i] += v, i += lowbit(i));
}
int sum(int i) {
    int s = 0;
    for (; i > 0; s += ar[i], i -= lowbit(i));
    return s;
}
int sum(int i,int j) {
    return sum(j) - sum(i-1);
}

queue <int> q;
int c[N][26],fi[N],cnt,tot,fa[N],pos[N];
vector <int> val[N];
vector <int> g[N];
vector <int> tr[N];
void ins(char *str) {
    int len=strlen(str), p=0;
    for(int i=0; i<len; i++) {
        if(str[i]=='B') {p=fa[p]; continue;}
        if(str[i]=='P') {val[p].push_back(++tot); pos[tot]=p; continue;}
        int v=str[i]-'a';
        if(!c[p][v]) c[p][v]=++cnt,tr[p].push_back(c[p][v]);
        fa[c[p][v]]=p;
        p=c[p][v];
    }
}
void build() {
    for(int i=0; i<26; i++) if(c[0][i]) fi[c[0][i]]=0, q.push(c[0][i]);
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        for(int i=0; i<26; i++)
            if(c[u][i]) fi[c[u][i]]=c[fi[u]][i], q.push(c[u][i]);
            else c[u][i]=c[fi[u]][i];
    }
}
void query(char *s) {
    int len=strlen(s);
    int p=0;
    for(int i=0; i<len; i++) {
        p=c[p][s[i]-'a'];
    }
}

char str[N];
int dfn[N],fin[N],ind,ans[N];

struct cquery {
    int x,y,id;
};
vector <cquery> qu[N];

void dfs(int p) {
    dfn[p]=++ind;
    for(int q:g[p]) dfs(q);
    fin[p]=ind;
}

void solve(int p) {
    add(dfn[p],1);
    for(cquery t:qu[p]) {
        ans[t.id]=sum(dfn[t.x],fin[t.x]);
    }
    for(int q:tr[p]) solve(q);
    add(dfn[p],-1);
}

signed main() {
    ios::sync_with_stdio(false);
    cin>>str;
    ins(str);
    build();
    for(int i=1;i<=cnt;i++) g[fi[i]].push_back(i);
    dfs(0);
    if(dbg) {
        cout<<"fail"<<endl;
        for(int i=0;i<=cnt;i++) {
            for(int j:g[i]) cout<<i<<"->"<<j<<endl;
        }
        cout<<"trans"<<endl;
        for(int i=0;i<=cnt;i++) {
            for(int j:tr[i]) cout<<i<<"->"<<j<<endl;
        }
        for(int i=0;i<=cnt;i++) cout<<dfn[i]<<" ";
        cout<<endl;
    }
    int m,x,y;
    cin>>m;
    for(int i=1;i<=m;i++) {
        cin>>x>>y;
        x=pos[x];
        y=pos[y];
        if(dbg) cout<<"translated "<<x<<" "<<y<<endl;
        qu[y].push_back({x,y,i});
    }
    solve(0);
    for(int i=1;i<=m;i++) {
        cout<<ans[i]<<endl;
    }
}
02-14 02:25