题解 CF710F【String Set Queries】

$\text{Link}$

这个题应该至少有 *2700 吧/kel

题意

维护一个字符串集合,支持三种操作共 $q$ 次:

  1. 加入字符串 $s$
  2. 删除已有字符串 $s$
  3. 查询集合中的所有字符串在给出的模板串 $s$ 中出现的次数

强制在线。

$q,\sum|s|\le10^6$.

思路

老师要我写题解。

删除是假的,可以看为两个集合分别代表加入和删除,求两个集合的答案差。

考虑询问形式很像 $\text{ACAM}$,如果没有强制在线,直接把所有的加入串拉出来建出 $\text{ACAM}$,加入就是子树加,查询就是在 $\text{Fail}$ 树上这个字符串对应的路径的点权和。

现在强制在线了,但如果我们知道之前所有的操作构成的 $\text{ACAM}$ 和对应的权值便可以简单查询。

考虑建立 $S$ 个 $\text{ACAM}$,当某个 $\text{ACAM}$ 达到某特定大小时不再加入字符串,新开一个 $\text{ACAM}$,否则将次串加入 $\text{ACAM}$ 并重构。每次询问遍历所有 $\text{ACAM}$ 求对应答案。发现每个字符串只会在重构中出现 $O(\dfrac{n}S)$ 次,询问有 $O(S)$ 个 $\text{ACAM}$,令 $S=O(\sqrt n)$ 则复杂度是 $O(n\sqrt n)$,无法达到我的要求。

考虑将大小相同的 $\text{ACAM}$ 合并,每次合并暴力合并 $\text{Trie}$,重建 $\text{Fail}$ 树并计算子树加带来的贡献,加入字符串时直接建立大小为 $1$ 的 $\text{ACAM}$。可以发现,每个 $\text{ACAM}$ 大小为 $2^i$,且 $i$ 互不相同。每个字符串只会合并 $O(\log n)$ 次,同时只会出现 $O(\log n)$ 个 $\text{ACAM}$。时间复杂度为 $O(n\log n)$,可以通过。

一般称这种方法为二进制分组,有时会有强大的作用。

代码(强制在线方法与题目描述不同,仅供参考):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff

}
const int N=1e6+10;
struct ACAM{
    int cnt,rts,tr[N][26],tr2[N][26];
    int rt[N],fail[N],val[N],siz[N];
    int delcnt,del[N];
    ll tmp[N];
    inline int newnode(){
        return delcnt?del[delcnt--]:++cnt;
    }
    inline void insert(char *s,int &rt){
        if(!rt) rt=newnode();
        int n=strlen(s+1);
        int cur=rt;
        for(int i=1;i<=n;i++){
            int c=s[i]-'a';
            if(!tr[cur][c])
                tr[cur][c]=newnode();
            cur=tr[cur][c];
        }
        val[cur]++;
    }
    inline void makefail(int rt){
        queue<int>q;
        fail[rt]=rt;
        for(int i=0;i<26;i++)
            if(tr[rt][i]){
                tr2[rt][i]=tr[rt][i];
                fail[tr[rt][i]]=rt;
                q.push(tr[rt][i]);
            }else{
                tr2[rt][i]=rt;
            }
        while(!q.empty()){
            int cur=q.front();
            q.pop();
            tmp[cur]=val[cur]+tmp[fail[cur]];
            for(int i=0;i<26;i++)
                if(tr[cur][i]){
                    tr2[cur][i]=tr[cur][i];
                    fail[tr[cur][i]]=tr2[fail[cur]][i];
                    q.push(tr[cur][i]);
                }else{
                    tr2[cur][i]=tr2[fail[cur]][i];
                }
        }
    }
    inline int merge(int x,int y){
        if(!x||!y) return x+y;
        val[x]+=val[y];
//      tmp[y]=0;
//      del[++delcnt]=y;
        for(int i=0;i<26;i++)
            tr[x][i]=merge(tr[x][i],tr[y][i]);//,tr[y][i]=0;
        return x;
    }
    inline void insert(char *s){
        siz[++rts]=1;
        insert(s,rt[rts]);
        while(siz[rts-1]==siz[rts]){
            siz[rts-1]+=siz[rts];
            rt[rts-1]=merge(rt[rts-1],rt[rts]);
            rt[rts]=0;
            rts--;
        }
        makefail(rt[rts]);
    }
    inline ll query(char *s){
        int n=strlen(s+1);
        ll ans=0; 
        for(int i=1;i<=rts;i++){
            int cur=rt[i];
            for(int j=1;j<=n;j++){
                cur=tr2[cur][s[j]-'a'];
                ans+=tmp[cur];
            }
        }
        return ans;
    }
}add,del; 
int q;
char str[N];
ll last;
/*
6
1 . 25
1 ab 2
3 abcab 2
2 ab 0
1 ab 2
3 ababab 0
*/
int main(){
    q=read();
    while(q--){
        int opt=read();
        readstr(str);
        int n=strlen(str+1);
        if(n==1&&str[1]=='.') n=0; 
        ll tmp=read()^last;
        str[n+1]=tmp+'a',str[n+2]=0;
        if(opt==1) add.insert(str);
        if(opt==2) del.insert(str);
        if(opt==3) write(last=add.query(str)-del.query(str)),putc('\n');
    }
    flush();
}

再见 qwq~

posted @ 2022-02-18 13:39  ffffyc  阅读(9)  评论(0)    收藏  举报  来源