【CF710F】String Set Queries(二进制分组+AC自动机)
- \(q\) 次询问,每次往字符串集合中插入/删除一个字符串,或询问集合中所有字符串在给定字符串中的出现次数。
- \(1\le q\le3\times10^5\),\(1\le\sum|s|\le3\times10^5\),强制在线
二进制分组
二进制分组的模板题。突然发现我好像从来没写过二进制分组,虽然挺早就知道它的理论?
首先把删除字符串改成插入一个权值为 \(-1\) 的字符串,那么就只要插入操作了。
而二进制分组的思想实际上非常简单,就是先把当前元素当作单独一个组,然后若当前组的大小和前一个组的大小相等就合并。
所谓合并,就是把这个组所有元素放一起重构一遍。
显然任意时刻只会有 \(\log n\) 个组,且一个元素至多经过 \(\log n\) 次合并,复杂度正确。
其实容易发现若当前有 \(x\) 个元素,那么得到的新组大小就是 \(\operatorname{lowbit}(x)\)。
本题中,对每个组的字符串建一个 AC 自动机,每次在所有组的 AC 自动机中询问一下即可。
代码:\(O(n\log n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 300000
#define LN 18
using namespace std;
string st,s[N+5];int v[N+5],Lg[N+5];
int Nt,Et,Ep[N+5];struct node {int V,F,S[26];}O[N+5];
class ACAutomation//AC自动机
{
private:
#define New() (Et?Ep[Et--]:++Nt)
#define D(x) (O[x].V=0,memset(O[x].S,0,sizeof(O[x].S)),Ep[++Et]=x)
int rt,nt,pl[N+5];
I void Ins(Cn string& s,CI v)//插入
{
RI i,x=rt,t,l=s.length();for(i=0;i^l;++i) !O[x].S[t=s[i]-'a']&&(O[x].S[t]=pl[++nt]=New()),x=O[x].S[t];O[x].V+=v;
}
int q[N+5];I void Get()//建AC自动机,求fail指针
{
RI i,k,H=1,T=0;for(i=0;i^26;++i) (O[rt].S[i]?O[q[++T]=O[rt].S[i]].F:O[rt].S[i])=rt;
W(H<=T) for(k=q[H++],i=0;i^26;++i) (O[k].S[i]?O[q[++T]=O[k].S[i]].F:O[k].S[i])=O[O[k].F].S[i];
for(i=1;i<=T;++i) O[q[i]].V+=O[O[q[i]].F].V;
}
public:
I void Bd(CI l,CI r)//对l~r的字符串建AC自动机
{
W(nt) D(pl[nt]),--nt;rt=pl[nt=1]=New();for(RI i=l;i<=r;++i) Ins(s[i],v[i]);Get();
}
I int Q()//询问子串权值和
{
RI i,x=rt,t,l=st.length(),g=0;for(i=0;i^l;++i) if(!O[x].S[t=st[i]-'a']) break;else x=O[x].S[t],g+=O[x].V;return g;
}
}AC[LN+1];
int main()
{
RI Qt,i,op,x,t,ct=0;for(i=1;(1<<i)<=N;++i) Lg[1<<i]=i;
scanf("%d",&Qt);W(Qt--) switch(scanf("%d",&op),cin>>st,op)
{
case 1:s[++ct]=st,v[ct]=1,AC[Lg[ct&-ct]].Bd(ct-(ct&-ct)+1,ct);break;
case 2:s[++ct]=st,v[ct]=-1,AC[Lg[ct&-ct]].Bd(ct-(ct&-ct)+1,ct);break;//删除转插入
case 3:x=ct,t=0;W(x) t+=AC[Lg[x&-x]].Q(),x-=x&-x;printf("%d\n",t),fflush(stdout);break;//在每个组中询问
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒