zoukankan      html  css  js  c++  java
  • 【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('
    ');}
    		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;//输出答案
    }
    
  • 相关阅读:
    教你不编程快速解析 JSON 数据
    教你在 Linux 下时光穿梭
    在Linux系统中使用Vim读写远程文件
    【高并发】如何实现亿级流量下的分布式限流?这些理论你必须掌握!!
    【高并发】高并发环境下构建缓存服务需要注意哪些问题?我和阿里P9聊了很久!
    【高并发】关于线程池,蚂蚁金服面试官问了我这些内容!!
    【高并发】关于乐观锁和悲观锁,蚂蚁金服面试官问了我这几个问题!!
    机器学习 | 简介推荐场景中的协同过滤算法,以及SVD的使用
    Python | Python初学者的自我修养,找到自己的方向
    LeetCode 86 | 链表基础,一次遍历处理链表中所有符合条件的元素
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/LOJ6041.html
Copyright © 2011-2022 走看看