历史行程

题意:
给出串,多组询问,问区间[l,r]的两个数[i,j],ij不同,以i,j结尾的前缀最长公共后缀最大是多少。
长度询问数1e5


很容易想到弄个SA,求出height数组
两个前缀的最长公共后缀对应着SA的位置之间height的最小值
考虑一个height值对序列的贡献,也就是要弄出跨过每一个height的两点l,r,并且lr之间不存在更小的。
就相当于在一个笛卡尔树上做子树合并,这个可以用启发式。
对于一个height,我们不能将所有点对找出来,事实上,对于在一边的一个点,只需要知道在另一边在它之后出现最早的,和在它之前出现最晚的,只有这两个是有用的,因此,在合并的时候,找到这样的点,就相当于求,已知 O ( n log ⁡ n ) O(n \log n) O(nlogn)个二维点,每次询问某种偏序比它大的点的最大权。离线扫描线处理, O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n)

#include
#include
#include
#include
#define N 100100
#define M 2001000
using namespace std;
int n,q,a[N],tax[N],rk[N],sa[N],tp[N],l[N],r[N],sz[N],h[N],st[N],top,ans[N],t,bl[N],cnt;
int s[N*4][2],tg[N*4],opl,opv;
bool cmp(int a,int b){return h[a]>h[b];}
struct pr{int l,r,x,t;}c[M];
bool Cmp(pr a,pr b){return a.l>b.l||(a.l==b.l&&a.t<b.t);}
set<int>b[N];
set<int>::iterator it;
void rsort(){
	for(int i=0;i<=n;++i)tax[i]=0;
	for(int i=1;i<=n;++i)++tax[rk[tp[i]]];
	for(int i=1;i<=n;++i)tax[i]+=tax[i-1];
	for(int i=n;i;--i)sa[tax[rk[tp[i]]]--]=tp[i];
}
#define pd(x,y,w) (tp[x]==tp[y]&&tp[x+w]==tp[y+w])
void SA(){
	for(int i=1;i<=n;++i)tp[i]=i,rk[i]=a[i];rsort();
	for(int p=1,m=1;p<n;m<<=1){
		p=m;for(int i=1;i<=m;++i)tp[i]=n-i+1;
		for(int i=1;i<=n;++i)if(sa[i]>m)tp[++p]=sa[i]-m;
		rsort();memcpy(tp,rk,sizeof tp);rk[sa[1]]=p=1;
		for(int i=2;i<=n;++i)rk[sa[i]]=pd(sa[i],sa[i-1],m)?p:++p;
	}
	for(int i=1,j=0;i<=n;h[rk[i++]]=j)
		for(j=j?j-1:j;a[i+j]==a[sa[rk[i]-1]+j];++j);
	for(int i=2;i<=n;++i){
		st[top+1]=0;
		while(top&&h[st[top]]>=h[i])top--;
		r[st[top]]=i;l[i]=st[top+1];st[++top]=i;
	}
}
void search(int x,int _l,int _r){
	if(!x){b[bl[_l]=++cnt].insert(sa[_l]);return;}
	search(l[x],_l,x-1);
	search(r[x],x,_r);
	sz[x]=sz[l[x]]+sz[r[x]];
	int p,q;
	p=bl[_l];q=bl[x];
	if(sz[l[x]]>sz[r[x]])swap(p,q);
	bl[_l]=q;
	while(!b[p].empty()){
		int v=*b[p].begin();b[p].erase(v);
		it=b[q].upper_bound(v);
		if(*--b[q].end()>v)c[++t]=(pr){v,*it,h[x],0};
		if(*b[q].begin()<v)c[++t]=(pr){*--it,v,h[x],0};
		b[q].insert(v);
	}
}
void upd(int x,int h,int t){
	if(h>=opl){tg[x]=max(tg[x],opv);return;}
	int m=h+t>>1;upd(x+x+1,m+1,t);
	if(opl<=m)upd(x+x,h,m);
}
void query(int x,int h,int t){
	opv=max(opv,tg[x]);
	if(h==t)return;
	int m=h+t>>1;
	if(opl<=m)query(x+x,h,m);else query(x+x+1,m+1,t);
}
int main(){
	scanf("%d %d\n",&n,&q);sz[0]=1;
	for(int i=1;i<=n;++i)a[n-i+1]=getchar()-47;
	for(int i=1;i<=q;++i){
		int l,r;scanf("%d %d",&l,&r);
		c[++t]=(pr){n-r+1,n-l+1,i,1};
	}SA();search(st[1],1,n);
	sort(c+1,c+t+1,Cmp);
	for(int i=1;i<=t;++i)if(c[i].t){
		opl=c[i].r;opv=0;
		query(1,1,n);ans[c[i].x]=opv;
	}else{
		opl=c[i].r;opv=c[i].x;
		upd(1,1,n);
	}
	for(int i=1;i<=q;++i)printf("%d\n",ans[i]);
}

你可能感兴趣的:(启发式算法,笛卡尔树,SA,线段树)