CF163E e-Government & [NOI2011] 阿狸的打字机 题解

CF163E e-Government
题意:给定 \(n\) 个模式串,最初所有模式串都处于活跃状态,有 \(m\) 次操作,每次可以改变一个模式串的状态,可以询问一个文本串中所有活跃的模式串的出现次数之和。\(n,m \le 10^5\),\(\sum |s| \le 10^6\)。

看见这个询问内容想到 AC 自动机,但是这题还要求我们能支持动态修改。

先考虑暴力是怎么写的,在自动机上遍历文本串,每到一个节点就跳 \(fail\),然后如果是一个串的结尾就计入一次,那就是考虑一个节点到根的 \(fail\) 链上有几个结尾的节点,换个角度考虑,思考一个节点作出了多少贡献。

我们把 \(fail_u\) 向 \(u\) 连边,形成一棵 \(fail\) 树,那一个节点如果是串的结尾,就可以对其子树内所有点作出贡献。预处理出每个串的结尾节点 \(pos_i\),然后求出 dfs 序,要求支持对 \([in_{pos_i},out_{pos_i}]\) 区间做加法,还要能单点查值。这样要删除一个串就只要把 \(pos_i\) 的子树减一就行了。

时间复杂度 \(O(\sum|s| \log \sum|t|)\)。

#include<bits/stdc++.h>
using namespace std;
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m;
const int N = 1e6+10;
string s,t;
int tr[N][27],fail[N],val[N],tot,pos[N];
vector<int>g[N];
struct trie{
    void ins(string s,int id){
        int p=0;
        for(int i=0;i<s.size();i++){
            int c=s[i]-'a';
            if(!tr[p][c])tr[p][c]=++tot;
            p=tr[p][c];
        }
        val[p]=1;
        pos[id]=p;
    }
    void build(){
        queue<int>q;
        for(int i=0;i<26;i++){
            if(tr[0][i]){
                g[0].push_back(tr[0][i]);
                q.push(tr[0][i]);
            }
        }
        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];
                    g[fail[tr[u][i]]].push_back(tr[u][i]);
                    q.push(tr[u][i]);
                }else{
                    tr[u][i]=tr[fail[u]][i];
                }
            }
        }
    }
}ac;
int cnt,dfn[N],rdfn[N],in[N],out[N];
void dfs(int x){
    dfn[x]=++cnt;
    rdfn[cnt]=x;
    in[x]=out[x]=cnt;
    for(auto v:g[x]){
        if(v==fail[x])continue;
        dfs(v);
    }
    out[x]=cnt;
}
int c[N],vis[N];
int lowbit(int x){
    return x&(-x);
}
void add(int p,int v){
    for(int i=p;i<=cnt;i+=lowbit(i)){
        c[i]+=v;
    }
}
int ask(int p){
    int res=0;
    for(int i=p;i;i-=lowbit(i))res+=c[i];
    return res;
}
long long query(string s){
    long long res=0;
    int p=0;
    for(int i=0;i<s.size();i++){
        int c=s[i]-'a';
        p=tr[p][c];
        res+=ask(in[p]);
    }
    return res;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>t;
        ac.ins(t,i);
    }
    ac.build();
    dfs(0);
    for(int i=1;i<=n;i++){
        int x=pos[i];
        vis[i]=1;
        add(in[pos[i]],1);
        if(out[pos[i]]<cnt)add(out[pos[i]]+1,-1);
    }
    while(m--){
        char op;
        cin>>op;
        int k;
        if(op=='+'){
            cin>>k;
            if(!vis[k]){
                add(in[pos[k]],1);
                if(out[pos[k]]<cnt)add(out[pos[k]]+1,-1);
                vis[k]=1;
            }
        }else if(op=='-'){
            cin>>k;
            if(vis[k]){
                add(in[pos[k]],-1);
                if(out[pos[k]]<cnt)add(out[pos[k]]+1,1);
                vis[k]=0;
            }
        }else{
            cin>>s;
            cout<<query(s)<<endl;
        }
    }
    return 0;
}

[NOI2011] 阿狸的打字机

发现给出的打字机打字过程就是在建立一棵 trie 树的过程。\(a\) 到 \(z\) 是往对应的儿子节点走,\(B\) 是回到父节点,\(P\) 则标记这个单词为结尾。

给出 \(m\) 次询问,询问第 \(x\) 个串在第 \(y\) 个里面出现了几次。由于只要单个的匹配,而且没有字符串询问长度总和的限制,这题再用上面的思路就会 TLE。

我们考虑离线挂点,把询问 \(x,y\) 都挂在 \(y\) 上,再模拟一次建树的过程,这时用一个树状数组标记当前单词中的点,每遇到结尾节点就处理询问,询问当前 \(x\) 的 \(fail\) 树上子树内有几个点被标记,这就是答案。只要支持单点加区间查询的 BIT 即可。

时间复杂度 \(O((|S|+m)\times \log|S|)\)。

#include<bits/stdc++.h>
using namespace std;
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int m,n=1;
const int N = 1e5+10;
string s,t[N];
int tr[N][26],fail[N],pos[N],tot,fa[N];
vector<int>g[N];
struct trie{
    void buildt(string s){
        int p=0;
        for(int i=0;i<s.size();i++){
            if(s[i]>='a'&&s[i]<='z'){
                if(!tr[p][s[i]-'a'])tr[p][s[i]-'a']=++tot;
                fa[tot]=p;
                p=tr[p][s[i]-'a'];
            }else if(s[i]=='B'){
                p=fa[p];
            }else{
                pos[n]=p;
                n++;
            }
        }
    }
    void build(){
        queue<int>q;
        for(int i=0;i<26;i++){
            if(tr[0][i]){
                g[0].push_back(tr[0][i]);
                q.push(tr[0][i]);
            }
        }
        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];
                    g[fail[tr[u][i]]].push_back(tr[u][i]);
                    q.push(tr[u][i]);
                }else{
                    tr[u][i]=tr[fail[u]][i];
                }
            }
        }
    }
}ac;
int cnt,in[N],out[N];
void dfs(int x){
    in[x]=out[x]=++cnt;
    for(auto v:g[x]){
        dfs(v);
    }
    out[x]=cnt;
}
int lowbit(int x){
    return x&(-x);
}
int c[N];
void add(int p,int v){
    for(int i=p;i<=cnt;i+=lowbit(i)){
        c[i]+=v;
    }
}
int ask(int p){
    int res=0;
    for(int i=p;i;i-=lowbit(i)){
        res+=c[i];
    }
    return res;
}
struct qy{
    int x,id;
};
vector<qy>q[N];
long long ans[N];
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>s;
    cin>>m;
    ac.buildt(s);
    n--;
    ac.build();
    dfs(0);
    for(int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        q[y].push_back({x,i});
    }
    int p=0;int cur=0;
    for(int i=0;i<s.size();i++){
        if(s[i]>='a'&&s[i]<='z'){
            p=tr[p][s[i]-'a'];
            add(in[p],1);
        }else if(s[i]=='B'){
            add(in[p],-1);
            p=fa[p];
        }else{
            ++cur;
            for(auto [x,id]:q[cur]){
                ans[id]=ask(out[pos[x]])-ask(in[pos[x]]-1);
            }
        }
    }
    for(int i=1;i<=m;i++){
        cout<<ans[i]<<endl;
    }
    return 0;
}
posted @ 2026-09-10 16:07  farenara_sayowell  阅读(6)  评论(0)    收藏  举报