【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;//输出答案
}

转载于:https://www.cnblogs.com/chenxiaoran666/p/LOJ6041.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值