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

【LOJ6041】「雅礼集训 2017 Day7」事情的相似度(用LCT维护SAM的parent树)

点此看题面

大致题意: 给你一个\(01\)串,每次询问前缀编号在一段区间内的两个前缀的最长公共后缀的长度。

离线存储询问

考虑将询问离线,按右端点大小用邻接表存下来(直接排序当然也可以啦)。

这样的好处是什么呢?

我们就可以对于每一个枚举到的右端点来对答案进行更新,然后再处理对应询问。

则对于当前一个已确定的右端点\(r\),显然询问\([l,r]\)的答案就是两个编号均大于等于\(l\)的点的答案的最大值(注意无须考虑右边界),而这可以直接拿线段树或树状数组统计。

由此可以发现,这样一来最大的好处就在于,不需要删除某种情况下的答案,而是可以直接叠加统计。

这样一来就可做了许多。

后缀自动机:题意转化

首先,这道题看起来很像\(SAM\)

所以,我们先无脑打个板子,建一个后缀自动机。

然后来考虑一下性质。

首先我们要知道,对于两个前缀,它们的最长公共后缀长度,就相当于\(parent\)树上这两个点的\(LCA\)\(Len\)

那么,问题似乎就转换成了\(parent\)树上一个点集内任意两点的\(LCA\)\(Len\)的最大值

然后就可以大力数据结构硬搞了。

\(LCT\)维护\(parent\)

考虑之前提到过的离线,则我们只需考虑每次加入一个新的节点对答案造成的影响。

结合题意转化,则其实也就相当于要求新加入的这个点与之前所有点\(LCA\)\(Len\)

注意\(LCA\),便可以想到\(LCT\)中的一个技巧:\(Access\)\(LCA\)。(关于这个技巧,有一道挺好的例题:【BZOJ4573】[ZJOI2016] 大森林

因此,修改只要在\(Access\)的过程中进行即可。

然后考虑如果一个点被作为新加入点与之前多个点的\(LCA\),则显然应该选择较后出现的进行单点修改(因为询问时我们区间询问某个位置到当前右端点间的最大值,则显然较后出现的可以影响到较先出现的答案)。

则我们可以记录一个\(V\),每次\(Access\)后把根所在的\(Splay\)内所有节点(即树中\(LCA(now,pre)\)到根节点的路径上的所有节点)的\(V\)改为新加入节点的编号。

具体实现详见代码。

代码

#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 Gmax(x,y) (x<(y)&&(x=(y)))
#define swap(x,y) (x^=y^=x^=y)
#define pb(x,y) (nxt[y]=lnk[x],lnk[x]=y) 
using namespace std;
int n,m,a[N+5],q[N+5],lnk[N+5],nxt[N+5],ans[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^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void readbit(int& x) {W(!D);x=c&1;}
		I void clear() {fwrite(FO,1,C,stdout),C=0;}
}F;
template<int SZ> class SuffixAutomation//后缀自动机
{
	private:
		int lst;struct Trie {int L,F,S[2];}O[SZ<<1];
	public:
		int tot,l[SZ<<1],f[SZ<<1];I SuffixAutomation() {tot=lst=1;}
		I void Record() {for(RI i=1;i<=tot;++i) l[i]=O[i].L,f[i]=O[i].F;}
		I int Insert(CI x)//插入节点
		{
			RI p=lst,q,k,now=lst=++tot;O[now].L=O[p].L+1;
			W(p&&!O[p].S[x]) O[p].S[x]=now,p=O[p].F;if(!p) return O[now].F=1,now;
			if(O[p].L+1==O[q=O[p].S[x]].L) return O[now].F=q,now;
			O[k=++tot]=O[q],O[k].L=O[p].L+1,O[now].F=O[q].F=k;
			W(p&&!(O[p].S[x]^q)) O[p].S[x]=k,p=O[p].F;return now;
		}
};
template<int SZ> class TreeArray//树状数组
{
	private:
		#define lowbit(x) ((x)&-(x))
		int a[SZ+5];
	public:
		I void Add(RI x,CI v) {W(x) Gmax(a[x],v),x-=lowbit(x);}//单点修改
		I int Qry(RI x) {RI t=0;W(x<=n) Gmax(t,a[x]),x+=lowbit(x);return t;}//区间询问
};
class LinkCutTree//LCT
{
	private:
		#define Upt(x,v) (O[x].f=O[x].V=v)
		#define PD(x) (O[x].f&&(Upt(O[x].S[0],O[x].f),Upt(O[x].S[1],O[x].f),O[x].f=0))
		#define IR(x) (O[O[x].F].S[0]^x&&O[O[x].F].S[1]^x)
		#define Wh(x) (O[O[x].F].S[1]==x)
		#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
		static Con int SZ=N<<1;int p[SZ+5],St[SZ+5];struct node {int f,V,F,S[2];}O[SZ+5];
		SuffixAutomation<N> SAM;TreeArray<N<<1> T;
		I void Ro(CI x)
		{
			RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x);
			O[x].F=p,Co(O[x].S[d^1],f,d),Co(f,x,d^1);
		}
		I void S(CI x)
		{
			RI f=x,T=0;W(St[++T]=f,!IR(f)) f=O[f].F;W(T) PD(St[T]),--T;
			W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(x)^Wh(f)?x:f),0),Ro(x);
		}
	public:
		I void Init(CI x,int* v) 
		{
			RI i;for(i=1;i<=n;++i) p[i]=SAM.Insert(v[i]);SAM.Record();
			for(i=1;i<=SAM.tot;++i) O[i].F=SAM.f[i];
		}
		I void Ac(RI x,CI v)//Access求LCA的过程,注意更新节点信息与答案
		{
			RI s;for(x=p[x],s=0;x;x=O[s=x].F) S(x),
				T.Add(O[x].V,SAM.l[x]),O[x].S[1]=s;Upt(s,v);
		}
		I int Query(CI x) {return T.Qry(x);}//询问答案
}LCT;
int main()
{
	RI Qtot,i,j,x;for(F.read(n,Qtot),i=1;i<=n;++i) F.readbit(a[i]);
	for(LCT.Init(n,a),i=1;i<=Qtot;++i) F.read(q[i],x),pb(x,i);//离线用邻接表存储
	for(i=1;i<=n;++i) for(LCT.Ac(i,i),j=lnk[i];j;j=nxt[j]) ans[j]=LCT.Query(q[j]);//枚举右端点,更新信息并处理询问
	for(i=1;i<=Qtot;++i) F.writeln(ans[i]);return F.clear(),0;//输出答案
}
posted @ 2019-04-08 20:29  TheLostWeak  阅读(402)  评论(0编辑  收藏  举报