题解 TJOI/HEOI2016 字符串
题面
解析
二分答案 (mid).
考虑后缀数组求出的数组,那么只要看 (sa) 数组里的
包含 (c) 开头的后缀的
一段满足最长公共前缀 (geq mid) 的区间里,有没有开头在 ([a,b-mid+1]) 中的后缀即可.
(可能上面那段话太模糊,请继续看下去)
具体来说,先求出一个 (l),满足 (sa) 数组中 ([l,c]) 这段区间的最长公共前缀都 (geq mid).
再求 (r),满足 (sa) 数组中 (c,r) 这段区间的最长公共前缀 (geq mid).
那么如果有开头
在 (sa) 数组中的 ([l,r]) 范围内,
且在原串中 ([a,b-mid+1]) 内
的后缀,
则答案 (geq mid).
那么 (l,r) 可以在求出 (height) 数组的 ( exttt{st}) 表后,二分答案求.
而判断 ([l,r]) 中是否有开头在 ([a,b-mid+1]) 中的点的后缀,
可以以 (rk) 作为下标,建主席树,
每次查询 ([a,b-mid+1]) 中的下标在 ([l,r]) 中的个数,如果 (>0) 则说明存在.
这题需要对后缀数组,主席树的灵活运用,并需要一些做题技巧(二分答案).
code
#include <iostream>
#include <cstring>
#include <cstdio>
#define ll long long
using namespace std;
inline int read(){
int sum=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c<='9'&&c>='0'){sum=sum*10+c-'0';c=getchar();}
return f*sum;
}
const int N=200005;
int n,Q;
char ss[N];
namespace SA{
int a[N],b[N],ca[N],cb[N],st[N];
int sa[N],rk[N],ht[N];
int lg[N],s[N<<1][18];//s_table
inline void init(){
int m=n;
for(int i=1;i<=n;i++) ca[a[i]=ss[i]-'a'+1]++;
for(int i=1;i<=m;i++) ca[i]+=ca[i-1];
for(int i=n;i>=1;i--) sa[ca[a[i]]--]=i;
rk[sa[1]]=1;
for(int i=2;i<=n;i++)
rk[sa[i]]=rk[sa[i-1]]+(a[sa[i]]!=a[sa[i-1]]);
for(int l=1;rk[sa[n]]<n;l<<=1){
for(int i=0;i<=m;i++) ca[i]=cb[i]=0;
for(int i=1;i<=n;i++){
ca[a[i]=rk[i]]++;
cb[b[i]=(i+l<=n? rk[i+l]:0)]++;
}
for(int i=1;i<=m;i++) ca[i]+=ca[i-1],cb[i]+=cb[i-1];
for(int i=1;i<=n;i++) st[cb[b[i]]--]=i;
for(int i=n;i>=1;i--) sa[ca[a[st[i]]]--]=st[i];
rk[sa[1]]=1;
for(int i=2;i<=n;i++)
rk[sa[i]]=rk[sa[i-1]]+(a[sa[i]]!=a[sa[i-1]]||b[sa[i]]!=b[sa[i-1]]);
}
int k=0;
for(int i=1;i<=n;i++){
if(rk[i]==1) continue;
if(k) k--;
int j=sa[rk[i]-1];
while(max(i,j)+k<=n&&ss[i+k]==ss[j+k]) k++;
ht[rk[i]]=k;
}
for(int i=1;i<=n;i++) s[i][0]=ht[i];
for(int j=1;j<18;j++)
for(int i=1;i<=n;i++)
if(i+(1<<(j-1))<=n) s[i][j]=min(s[i][j-1],s[i+(1<<(j-1))][j-1]);
lg[0]=-1;
for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
}
inline int lcp(int l,int r){
if(l>r) return 0x3f3f3f3f;
int k=lg[r-l+1];
return min(s[l][k],s[r-(1<<k)+1][k]);
}
};
using namespace SA;
namespace tr{
struct tree{int ls,rs,sum;}t[N*40];
int rt[N],tot;
inline void insert(int o,int &p,int l,int r,int x){
p=++tot;t[p]=t[o];t[p].sum++;
if(l==r) return ;
int mid=(l+r)>>1;
if(x<=mid) insert(t[o].ls,t[p].ls,l,mid,x);
else insert(t[o].rs,t[p].rs,mid+1,r,x);
}
inline int query(int p1,int p2,int l,int r,int ql,int qr){
if(ql>qr||!(t[p2].sum-t[p1].sum)) return 0;
if(l>=ql&&r<=qr) return t[p2].sum-t[p1].sum;
int mid=(l+r)>>1,ret=0;
if(ql<=mid) ret+=query(t[p1].ls,t[p2].ls,l,mid,ql,qr);
if(qr>mid) ret+=query(t[p1].rs,t[p2].rs,mid+1,r,ql,qr);
return ret;
}
};
using namespace tr;
signed main(){
n=read();Q=read();
scanf("%s",ss+1);
init();
for(int i=1;i<=n;i++) insert(rt[i-1],rt[i],1,n,rk[i]);
while(Q--){
int a=read(),b=read();
int c=read(),d=read();
int L=0,R=min(b-a+1,d-c+1),ans=0;
while(L<=R){
int mid=(L+R)>>1;
int pl=0,pr=0;
int l=1,r=rk[c];
while(l<=r){
int md=(l+r)>>1;
if(lcp(md+1,rk[c])>=mid) r=md-1,pl=md;
else l=md+1;
}
l=rk[c];r=n;
while(l<=r){
int md=(l+r)>>1;
if(lcp(rk[c]+1,md)>=mid) l=md+1,pr=md;
else r=md-1;
}
if(query(rt[a-1],rt[(mid? b-mid+1:b)],1,n,pl,pr)) ans=mid,L=mid+1;
else R=mid-1;
}
printf("%d
",ans);
}
return 0;
}