【BZOJ3881】[COCI2015] Divljak(AC自动机+树状数组)

点此看题面

大致题意: 已知(n)个字符串和一个空的字符串集。两种操作:往字符串集中加入一个字符串;询问(n)个字符串中的某个字符串是字符串集中多少个字符串的子串。

前言

一看完题面,我觉得这是道水题,但等真正开始写的时候,我发现,这真是一道水题。。。

然而,这道题不光数据范围有问题,还卡内存,这就非常令人绝望了。

(500MB)的内存限制我最后好不容易卡在了(494.88MB)。。。

卡内存三部曲:

  • 把倍增(LCA)改成树剖(LCA)
  • 发现把单向边智障地开了两倍数组,赶紧改了。
  • 异想天开地把树剖中重儿子数组重复利用作链首数组。

一道类似的题目+子串的转化

有一道和这题非常像的题目:【BZOJ2434】[NOI2011] 阿狸的打字机

我们需要知道,子串就等同于前缀的后缀,而在(AC)自动机上有这样两个性质:

  • (A)串是(B)串的前缀,说明(A)串在(AC)自动机上对应的节点在根节点到(B)串的路径上。
  • (A)串是(B)串的后缀,说明(B)串在(AC)自动机上对应的节点跳了若干次(fail)指针后能够到达(A)串。

于是,如果我们把(AC)自动机的(fail)树通过(dfs)序转化为数列,然后插入一个字符串就相当于把(AC)自动机上根到其对应节点的链上的所有点打个标记,询问就相当于询问(fail)树中其对应节点子树内的标记种数。

一些细节

考虑如何处理标记。

可以使用差分,将所有要打标记的点按(dfs)序排序后,给每个点的权值加(1),然后给相邻两个点(LCA)的权值减(1)。容易发现这样每个字符串的贡献只会被计算一次了。

考虑如何处理询问。

这太显然了。子树在数列上相当于一个区间,只要用树状数组,就可以方便地实现单点修改、区间查询。

然后似乎就没啥了吧,具体实现可见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define swap(x,y) (x^=y^=x^=y) 
using namespace std;
int n,m,p[N+5];string s[N+5];struct data {int op,id;}q[N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
		I void reads(string& x) {x="";W(isspace(c=tc()));W(x+=c,!isspace(c=tc())&&~c);}
		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Tp I void writeln(Con Ty& x) {write(x),pc('
');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class AcAutomation//AC自动机
{
	private:
		#define SZ 4000000
		int Nt,S[SZ+5][26],F[SZ+5],q[SZ+5];
		class FailTree//Fail树
		{
			private:
				#define LG 21
				int d,dfn[SZ+5],dep[SZ+5],fa[SZ+5],tp[SZ+5],sz[SZ+5];
				int n,ee,lnk[SZ+5];struct edge {int to,nxt;}e[SZ];
				struct data
				{
					int x,y;I data(CI a=0,CI b=0):x(a),y(b){}
					I bool operator < (Con data& o) Con {return y<o.y;}
				}s[SZ+5];
				class TreeArray//树状数组
				{
					private:
						int n,a[SZ+5];
					public:
						I void Init(CI x) {n=x;}I void U(RI x,CI v) {W(x<=n) a[x]+=v,x+=x&-x;}//单点修改
						I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//区间求值
				}T;
				I void dfs1(CI x)//树剖第一遍dfs
				{
					sz[x]=1;for(RI i=lnk[x];i;i=e[i].nxt) dep[e[i].to]=dep[fa[e[i].to]=x]+1,
						dfs1(e[i].to),sz[x]+=sz[e[i].to],sz[e[i].to]>sz[tp[x]]&&(tp[x]=e[i].to);
				}
				I void dfs2(CI x,CI p)//树剖第二遍dfs
				{
					dfn[x]=++d,tp[x]&&(dfs2(tp[x],p),0);
					for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^tp[x]&&(dfs2(e[i].to,e[i].to),0);tp[x]=p;
				}
				I int LCA(RI x,RI y)//树剖LCA
				{
					W(tp[x]^tp[y]) dep[tp[x]]>dep[tp[y]]?x=fa[tp[x]]:y=fa[tp[y]];
					return dep[x]<dep[y]?x:y;
				}
			public:
				I void Init(CI x) {T.Init(n=x),dfs1(1),dfs2(1,1);}//预处理
				I void Add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}
				I void U(int *q,CI n)//修改
				{
					RI i;for(i=1;i<=n;++i) s[i]=data(q[i],dfn[q[i]]);sort(s+1,s+n+1);//按dfs序排序
					for(T.U(s[1].y,1),i=1;i^n;++i) T.U(s[i+1].y,1),T.U(dfn[LCA(s[i].x,s[i+1].x)],-1);//差分
				}
				I int Q(CI x) {return T.Q(dfn[x]+sz[x]-1)-T.Q(dfn[x]-1);}//求子树权值和
		}P;
	public:
		I AcAutomation() {Nt=1;}
		I int Ins(Con string& s)//插入字符串
		{
			RI i,l=s.length(),x=1,nxt;for(i=0;i^l;++i)
				!S[x][nxt=s[i]-97]&&(S[x][nxt]=++Nt),x=S[x][nxt];
			return x;
		}
		I void Build()//建AC自动机
		{
			RI i,k,H=1,T=0;for(i=0;i^26;++i) (S[1][i]?F[q[++T]=S[1][i]]:S[1][i])=1;
			W(H<=T) for(k=q[H++],i=0;i^26;++i) (S[k][i]?F[q[++T]=S[k][i]]:S[k][i])=S[F[k]][i];
			for(i=2;i<=Nt;++i) P.Add(F[i],i);P.Init(Nt);//建Fail树
		}
		I void U(Con string& s) {RI i,l=s.length(),x=1,t=0;for(i=0;i^l;++i) q[++t]=x=S[x][s[i]-97];P.U(q,t);}//修改
		I int Q(CI x) {return P.Q(x);}//询问
}AC;
int main()
{
	RI i;for(F.read(n),i=1;i<=n;++i) F.reads(s[0]),p[i]=AC.Ins(s[0]);//p记录对应点
	RI t=0;for(F.read(m),i=1;i<=m;++i) F.read(q[i].op),//离线存下询问,以建AC自动机
		q[i].op==1?(F.reads(s[q[i].id=++t]),AC.Ins(s[t])):(F.read(q[i].id),0);
	for(AC.Build(),i=1;i<=m;++i)
		q[i].op==1?AC.U(s[q[i].id]):F.writeln(AC.Q(p[q[i].id]));//处理操作
	return F.clear(),0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ3881.html