我们有一个经典模型:
两个串的最长公共后缀长度,是后缀树中两点 LCA 的深度.
直接求 LCA 似乎有些困难,不妨这样想 :
设两个串在后缀树中对应的点分别为 $a,b$,将 $a$ 到根的路径涂色,$b$ 向根爬,遇到的第一个涂色点即为 $a$ 与 $b$ 的 LCA.
我们用 $LCT$ 来维护 这颗树,涂色操作直接 $Access$ 并区间赋值.
此时树的形态是怎样的 ?
$a$ 点到根的路径是一个重链,$b$ 向上爬肯定会爬到 $a$ 所在的重链上.
直接爬肯定会很慢,不妨直接 $Access(b)$ ?
考虑 $Access$ 时的语句 : $y=x,x=f_{x}$.
我们在 $Access(b)$ 过程中碰到的第一个被涂色的点其实就是我们要求的 LCA.
尤其当被涂色的点变多的时候,每次 $Access$ 的复杂度就是均摊 $log_{2}{n}$ 的了.
是不是十分神奇 ?
观察这部分代码:
void Access(int x,int co)
{ int t=0; while(x) { splay(x); if(pos[x]) tr::update(1,n,1,pos[x],dis[x]); pos[x]=co, rson=t, t=x,x=f[x]; } }
我们一边 $Access$ ,一边将该询问点向上爬,每次遇到一个新的重链就将重链染成当前颜色(区间中的下标).可能会好奇为什么只更新那个下标更小的呢 ?
因为如果更新下标大的,而不更新下标小的,会出现这种情况:
当前询问区间左端点大于下标小的那个,那么显然下标大的继承不了下标小的的贡献.
为了避免这种情况,也就是说希望贡献给对未来没有影响的那个,我们只更新给下标更小的那个. (如果询问左端点小于下标小的点的话一定是能覆盖到的)
而下标大的点如何处理呢 ?
既然不对答案做贡献,我们将遇到的每一个重链都染成更大的下标. (显然我们以后要染的下标肯定都会大于该下标,所以肯定下标越大越好).
最难的部分处理完毕了,查询的时候直接在对应的线段树里查一个区间最大值即可.
#include<bits/stdc++.h> #define maxn 200003 using namespace std; void setIO(string s) { string in=s+".in", out=s+".out"; freopen(in.c_str(),"r",stdin); // freopen(out.c_str(),"w",stdout); } int n,Q; int dis[maxn],track[maxn]; char str[maxn]; namespace tr { int maxv[maxn<<2]; void update(int l,int r,int x,int k,int p) { if(l==r) { maxv[x]=p; return; } int mid=(l+r)>>1; if(k<=mid) update(l,mid,(x<<1),k,p); else update(mid+1,r,(x<<1)|1,k,p); maxv[x]=max(maxv[x<<1],maxv[(x<<1)|1]); } int query(int l,int r,int x,int L,int R) { if(l>=L&&r<=R) return maxv[x]; int mid=(l+r)>>1,tmp=0; if(L<=mid) tmp=max(tmp,query(l,mid,x<<1,L,R)); if(R>mid) tmp=max(tmp,query(mid+1,r,(x<<1)|1,L,R)); return tmp; } }; namespace tree { #define lson ch[x][0] #define rson ch[x][1] int f[maxn],ch[maxn][30],pos[maxn],sta[maxn]; int get(int x) { return ch[f[x]][1]==x; } int isrt(int x) { return !(ch[f[x]][0]==x||ch[f[x]][1]==x); } void pushdown(int x) { if(!x)return; if(pos[x]) { if(lson) pos[lson]=pos[x]; if(rson) pos[rson]=pos[x]; } } void rotate(int x) { int old=f[x], fold=f[old],which=get(x); if(!isrt(old)) ch[fold][ch[fold][1]==old]=x; ch[old][which]=ch[x][which^1],f[ch[old][which]]=old; ch[x][which^1]=old,f[old]=x,f[x]=fold; } void splay(int x) { int u=x,v=0,fa; sta[++v]=u; while(!isrt(u)) sta[++v]=f[u],u=f[u]; while(v) pushdown(sta[v--]); for(u=f[u];(fa=f[x])!=u;rotate(x)) if(f[fa]!=u) rotate(get(fa)==get(x)?fa:x); } void Access(int x,int co) { int t=0; while(x) { splay(x); if(pos[x]) tr::update(1,n,1,pos[x],dis[x]); pos[x]=co, rson=t, t=x,x=f[x]; } } }; namespace SAM { int last,tot; int ch[maxn][30],f[maxn]; void init() { last=tot=1; } void ins(int c,int o) { int np=++tot,p=last; last=np,dis[np]=dis[p]+1; while(p&&!ch[p][c]) ch[p][c]=np,p=f[p]; if(!p) f[np]=1; else { int q=ch[p][c]; if(dis[q]==dis[p]+1) f[np]=q; else { int nq=++tot; dis[nq]=dis[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); f[nq]=f[q], f[np]=f[q]=nq; while(p&&ch[p][c]==q) ch[p][c]=nq,p=f[p]; // 割裂操作 } } track[o]=np; } }; int answer[maxn]; struct OPT { int l,r,id; }opt[maxn]; bool cmp(OPT a,OPT b) { return a.r<b.r; } int main() { // setIO("input"); SAM::init(); scanf("%d%d",&n,&Q); scanf("%s",str+1); for(int i=1;i<=n;++i) SAM::ins(str[i]-'0',i); for(int i=2;i<=SAM::tot;++i) tree::f[i]=SAM::f[i]; for(int i=1;i<=Q;++i) scanf("%d%d",&opt[i].l,&opt[i].r),opt[i].id=i; sort(opt+1,opt+1+Q,cmp); for(int i=1,j=1;i<=n;++i) { tree::Access(track[i], i); // 打上 i 点的标记 while(opt[j].r==i && j<=Q) { answer[opt[j].id]=tr::query(1,n,1,opt[j].l,opt[j].r); ++j; } } for(int i=1;i<=Q;++i) printf("%d\n",answer[i]); return 0; }