正题
首先很容易可以想到一个在fail树上跳的方法,先找到r的结束节点,向上跳,对于每一个祖先x,找最大的i,满足i<l+len[x]且i<r,所有这样的i取最大值,就可以知道border的最大右端点.
但是这样每次操作是nlogn的,(也许可以优化一个根号),但时间复杂度依然很大.
我们考虑令每次这样的询问与len[x]无关,也就是变成i-len[x]<l且i<r,一眼就可以知道这个东西也可以主席树维护,维护一个以i为下标,i-len[x]为权值的线段树即可.
我们先用线段树合并维护出每个点的right集合.
接着,我们考虑使用重链剖分来优化这个过程,对于一个点x,同时维护重链剖分上的一棵主席树,主席树中插入的是自身的right和轻儿子的right,为什么不插重儿子的right?因为跳上来的时候会在当前节点暴力算一遍子树的答案(这里的暴力指的是第一行的线段树维护)我们们只需要知道当前节点到这条重链顶端的轻儿子和自身答案即可.
由于重链上的线段树与len是无关的,询问的时候无区别,重点就是运用了这个性质.
代码还是比较恶心的,打+调差不多两个小时.
可以仔细研究一下代码在算答案的时候为什么是一个log?
#pragma GCC optimize("O2")
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
const int N=400010;
int T,ls[20000010],rs[20000010],mmin[20000010],rt[N][2],n;//全 重
int ans;
struct edge{
int y,nex;
}e[N<<1];
int first[N],len=0;
void insq(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
void insert(int&now,int x,int l=1,int r=n){//全 插入
now=++T;
if(l==r) return ;
int mid=(l+r)/2;
if(x<=mid) insert(ls[now],x,l,mid);
else insert(rs[now],x,mid+1,r);
}
int merge(int x,int y){//全 合并
if(!x || !y) return x+y;
int now=++T;
ls[now]=merge(ls[x],ls[y]);
rs[now]=merge(rs[x],rs[y]);
return now;
}
void ins(int&now,int las,int x,int t,int l=1,int r=n){//重 插入
now=++T;
if(l==r) {mmin[now]=min(t,mmin[las]);return ;}
int mid=(l+r)/2;
if(x<=mid) rs[now]=rs[las],ins(ls[now],ls[las],x,t,l,mid);
else ls[now]=ls[las],ins(rs[now],rs[las],x,t,mid+1,r);
mmin[now]=min(mmin[ls[now]],mmin[rs[now]]);
}
void gi(int now,int&x,int y,int l=1,int r=n){//将 全 中的now,插入 重 中的x中
if(now==0) return ;
if(l==r) {ins(x,x,l,l-y);return ;}
int mid=(l+r)/2;
gi(ls[now],x,y,l,mid);
gi(rs[now],x,y,mid+1,r);
}
int ga(int now,int x,int y,int t,int l=1,int r=n){//重 中mmin[i]<x的最大的i
if(mmin[now]>=t) return 0;
if(l==r) return r;
int mid=(l+r)/2;
if(y<=mid) return ls[now]?ga(ls[now],x,y,t,l,mid):0;
else if(mid<x) return rs[now]?ga(rs[now],x,y,t,mid+1,r):0;
else{
int tmp=rs[now]?ga(rs[now],mid+1,y,t,mid+1,r):0;
if(tmp) return tmp;
return ls[now]?ga(ls[now],x,mid,t,l,mid):0;
}
}
int gas(int now,int x,int y,int l=1,int r=n){//全 中x到y中最大的i
if(l==r) return l;
int mid=(l+r)/2;
if(y<=mid) return ls[now]?gas(ls[now],x,y,l,mid):0;
else if(mid<x) return rs[now]?gas(rs[now],x,y,mid+1,r):0;
else{
int tmp=rs[now]?gas(rs[now],mid+1,y,mid+1,r):0;
if(tmp) return tmp;
return ls[now]?gas(ls[now],x,mid,l,mid):0;
}
}
struct Suffix_AutoMaton{
char s[N];
int len[N],tot,las,ch[N][26],sum[N],a[N],ed[N],rig[N];
int fa[N],top[N],son[N],op[N];
void extend(int pos,int c){
int x=++tot,p=las;len[x]=len[p]+1;las=x;ed[pos]=x;rig[x]=pos;
insert(rt[x][0],pos);
while(p!=-1 && !ch[p][c]) ch[p][c]=x,p=fa[p];
if(p==-1) return ;
if(len[ch[p][c]]==len[p]+1) fa[x]=ch[p][c];
else{
int q=++tot,tmp=ch[p][c];len[q]=len[p]+1;
fa[q]=fa[tmp];fa[tmp]=fa[x]=q;
for(int i=0;i<26;i++) ch[q][i]=ch[tmp][i];
while(p!=-1 && ch[p][c]==tmp) ch[p][c]=q,p=fa[p];
}
}
void build(){
scanf("%s",s+1);n=strlen(s+1);fa[0]=-1;mmin[0]=1e9;
for(int i=1;i<=n;i++) extend(i,s[i]-'a');
for(int i=1;i<=tot;i++) sum[len[i]]++,insq(fa[i],i);
for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
for(int i=1;i<=tot;i++) a[sum[len[i]]--]=i;
}
void dfs(int x,int tp){
top[x]=tp;
if(rig[x]) ins(rt[x][1],rt[x][1],rig[x],0);
for(int i=first[x];i!=0;i=e[i].nex) if(e[i].y!=son[x])
gi(rt[e[i].y][0],rt[x][1],len[x]),dfs(e[i].y,e[i].y);
if(son[x]) rt[son[x]][1]=rt[x][1],dfs(son[x],tp);
}
void prepare(){
for(int i=tot;i>=1;i--) if(fa[a[i]]){
int x=a[i];
op[x]++;op[fa[x]]+=op[x];
if(op[x]>op[son[fa[x]]]) son[fa[x]]=x;
rt[fa[x]][0]=merge(rt[fa[x]][0],rt[x][0]);
}
for(int i=first[0];i!=0;i=e[i].nex) dfs(e[i].y,e[i].y);
}
void solve(){
int q,l,r;
scanf("%d",&q);
while(q--){
scanf("%d %d",&l,&r);
int now=ed[r];ans=0;
while(now!=0){
if(r-1){
ans=max(ans,rt[now][0]?gas(rt[now][0],1,min(l+len[now]-1,r-1)):0);
ans=max(ans,rt[now][1]?ga(rt[now][1],1,r-1,l):0);
}
now=fa[top[now]];
}
printf("%d\n",max(ans-l+1,0));
}
}
}SAM;
int main(){
SAM.build();SAM.prepare();SAM.solve();
}