把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;
}
posted @ 2022-04-21 19:31  TheLostWeak  阅读(67)  评论(0编辑  收藏  举报