Loj #6041. 「雅礼集训 2017 Day7」事情的相似度

发布时间 2023-06-13 22:51:39作者: Linnyx

做到这题,发现自己对\(SAM\)的一些性质还不知道,特此记录。
题目要求01字符串区间内前缀的最长公共后缀
由SAM parent tree性质可知,2个前缀的最长公共后缀就是它们在parent tree上lca的len值
如何去感性理解
我们知道,在parent tree上每个节点都代表了一个endpos等价类,由后缀链接将他们连接起来,并且从根顺着向上走其实是一个不断从开头删去字符的过程,佐张图以供理解:
image

如果我们将一个前缀不停删去它的前缀(其实就是跳它的后缀)的过程看作是在parent tree上向根跳跃,并在所经历的节点上打上标记,那么,当另一个前缀也重复上述过程时,碰到的第一个有标记的节点的len,即为两前缀的最长公共后缀

前缀最长公共前缀解决了,现在我们想想如何维护这个跳后缀的过程。
发现这其实是一个根到目标节点的链覆盖,发现重链剖分并不能很好维护标记。
考虑实链剖分,联想到LCT的access操作:将根到u的路径变为一条实链。
正是我们需要的链覆盖(可脑补一下过程
LCT上只需要维护一下颜色覆盖和标记即可(根不变甚至不需要反转,pushup,link,cut

现在我们还有最后一个棘手的要求:区间
既然可以离线,考虑扫描线,将询问挂在右端点上。
先将SAM建好,明确parent tree的形态,从左至右加入前缀。
每次access时查看最近一次访问此节点的标记,并以此标记(不是自己),在任意一个可维护区间最大值的数据结构上更新区间后缀最大值。
然后贪心的覆盖上自己的标。
查询即在处理完R后询问维护的 \(L-N\) 的后缀最大值即可。
关于为什么直接覆盖,可以想到,每次是查询区间后缀最大值,端点越靠右,能影响更新到的区间就越多。
代码细节实现如下:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int rd(void){
	char ch=getchar();int f=1,res=0;
	while(!isdigit(ch)){
		if(ch=='-')f=0;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=res*10+(ch-'0');
		ch=getchar();
	}
	return f?res:-res;
}
const int N=4e5+10;
int ch[N][2],links[N],len[N],tot=1,pos[N];
int last=1,tr[N][2],n,m;
void sam_extend(int x,int id){
	int cur=++tot;
	len[cur]=len[last]+1;
	pos[id]=tot;
	int p=last;
	while(p&&tr[p][x]==0){
		tr[p][x]=cur;
		p=links[p];
	}
	if(p==0){
		links[cur]=1;
	}else{
		int q=tr[p][x];
		if(len[p]+1==len[q]){
			links[cur]=q;
		}
		else{
			int cl=++tot;
			memcpy(tr[cl],tr[q],sizeof(tr[q]));
			links[cl]=links[q];
			len[cl]=len[p]+1;
			while(p&&tr[p][x]==q){
				tr[p][x]=cl;
				p=links[p];
			}
			links[q]=links[cur]=cl;
		}
	}
	last=cur;
}
int f[N];
struct BIT{
	int t[N];
	inline int lowbit(int x){return x&(-x);}
	void upd(int x,int v){
		x=n-x+1;
		while(x<=n){
			t[x]=max(t[x],v);
			x+=lowbit(x);
		}
	} 
	inline int query(int x){
		int res=0;
		x=n-x+1;
		while(x){
			res=max(res,t[x]);
			x-=lowbit(x);
		}
		return res;
	}
}bit;
inline int nrt(int x){
	return (ch[f[x]][0]==x||ch[f[x]][1]==x);
}
inline int son(int x){
	return x==ch[f[x]][1];
}
void rotate(int x){
	int y=f[x],z=f[y];
	int kx=son(x),ky=son(y),w=ch[x][kx^1];
	if(nrt(y))ch[z][ky]=x;
	ch[x][kx^1]=y;
	ch[y][kx]=w;
	f[x]=z;f[y]=x;
	if(w)f[w]=y;
}
int tg[N],col[N];	
void change(int x,int v){
	tg[x]=col[x]=v;
}
void pd(int x){
	if(!tg[x])return ;
	if(ch[x][0])change(ch[x][0],tg[x]);
	if(ch[x][1])change(ch[x][1],tg[x]);
	tg[x]=0;
}
int sta[N];
void splay(int x){
	int now=x,top=0;
	sta[++top]=x;
	while(nrt(now))sta[++top]=now=f[now];
	while(top)pd(sta[top--]);
	while(nrt(x)){
		int y=f[x],z=f[y];
		if(nrt(y))
			(son(y)^son(x))?rotate(x):rotate(y);
		rotate(x);
	}
}
void access(int x,int v){
	int pre;
	for(pre=0;x;x=f[pre=x]){
		splay(x);
		bit.upd(col[x],len[x]);
		ch[x][1]=pre;
	}
	splay(pre);
	change(pre,v);
}
char s[N];
int ans[N];
#define pii pair<int,int>
vector<pii> G[N];
int main(){
	n=rd();m=rd();scanf("%s",s+1);
	for(int i=1;i<=n;i++)sam_extend(s[i]-'0',i);
	for(int i=1,l,r;i<=m;i++){
		l=rd();r=rd();
		G[r].push_back({l,i});
	}
	for(int i=2;i<=tot;i++){
		f[i]=links[i];
		//printf("* %d\n",len[i]);
		//printf("%d %d\n",links[i],i);
	}		
	for(int i=1;i<=n;i++){
		access(pos[i],i);
		for(pii j:G[i]){
			ans[j.second]=bit.query(j.first);	
		}
	}
	for(int i=1;i<=m;i++){
		printf("%d\n",ans[i]);
	}
	return 0;
}