• 【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;
    }
    
  • 相关阅读:
    TabControl
    Loading
    Dialog
    Combobox
    Markdown编辑器Editor.md使用方式
    XSS攻击
    跨域解决方案及实现
    angular4 自定义表单组件
    angular4 Form表单相关
    js 详解setTimeout定时器
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ3881.html
Copyright © 2020-2023  润新知