4556: [Tjoi2016&Heoi2016]字符串
Time Limit: 20 Sec Memory Limit: 128 MB
Submit: 179 Solved: 94
[Submit][Status][Discuss]
Description
佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了
一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CE
O,嫁给高富帅,走上人生巅峰。每个问题均有a,b,c,d四个参数,问你子串s[a..b]的所有子串和s[c..d]的最长公
共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?
Input
输入的第一行有两个正整数n,m,分别表示字符串的长度和询问的个数。接下来一行是一个长为n的字符串。接下来
m行,每行有4个数a,b,c,d,表示询问s[a..b]的所有子串和s[c..d]的最长公共前缀的最大值。1<=n,m<=100,000,
字符串中仅有小写英文字母,a<=b,c<=d,1<=a,b,c,d<=n
Output
对于每一次询问,输出答案。
Sample Input
5 5
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4
Sample Output
1
1
2
2
2
开始想了一种
O(logn)
(但是有些问题),大致是这样的。
可以发现在求
lcp
的时候用的是区间最小值,这样假如当求一个位置的最大
lcp
的时候,那肯定是找这个位置
rank
的前驱和后继是最优的,因为再往前找的话答案是不增的。
想到这点之后就直接写了个主席树。
主席树中可以存区间的元素个数和,查询的时候先查询一下区间和,再在区间中像平衡树那样看看需要往哪边走,就能
logn
的找到前驱后继了。
但是这样直接找前驱后继会有个问题,比如这组数据:
5 1
aaaab
1 2 3 5
这组数据在找后继的时候找的是
aaab
这个后缀,但是由于这个有区间长度的限制,所以最后找到的答案是
1
。但是更优的方案是找
为了解决这个问题,就需要在外面套个二分答案,这个二分就相当于在选了
时间复杂度是
O(mlog2n)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010;
const int M=4000000;
int n,m,o,s[N],t1[N],t2[N],c[N],sa[N],rank[N],height[N],st[N][20],Log[N],root[N],l[M],r[M],sum[M],siz;
inline int in(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
inline bool cmp(int *y,int p,int q,int k){
int o0=(p+k>=n)?-1:y[p+k];
int o1=(q+k>=n)?-1:y[q+k];
return o0==o1&&y[p]==y[q];
}
inline void build_sa(){
int i,k,p,*x=t1,*y=t2;
for(o=27,i=0;i<o;++i) c[i]=0;
for(i=0;i<n;++i) ++c[x[i]=s[i]];
for(i=1;i<o;++i) c[i]+=c[i-1];
for(i=n-1;~i;--i) sa[--c[x[i]]]=i;
for(k=1;k<=n;k<<=1){
for(p=0,i=n-k;i<n;++i) y[p++]=i;
for(i=0;i<n;++i) if(sa[i]>=k) y[p++]=sa[i]-k;
for(i=0;i<o;++i) c[i]=0;
for(i=0;i<n;++i) ++c[x[y[i]]];
for(i=1;i<o;++i) c[i]+=c[i-1];
for(i=n-1;~i;--i) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
x[sa[0]]=0;o=1;
for(i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k)?o-1:o++;
if(o>=n) break;
}
}
inline void build_height(){
int i,k=0,j;
for(i=0;i<n;++i) rank[sa[i]]=i;
for(i=0;i<n;++i){
if(!rank[i]) continue;
k=k?--k:k;
j=sa[rank[i]-1];
while(s[j+k]==s[i+k]) ++k;
height[rank[i]]=k;
}
memset(st,127/3,sizeof(st));
for(i=0;i<n;++i) st[i][0]=height[i];
for(j=1;j<=20;++j)
for(i=0;i+(1<<(j-1))<n;++i)
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
for(j=0,i=1;i<=n;++i){
if((1<<(j+1))<=i) ++j;
Log[i]=j;
}
}
inline int LCP(int x,int y){
if(x>y) swap(x,y);
int k=Log[y-x];++x;
return min(st[x][k],st[y-(1<<k)+1][k]);
}
#define mid (L+R)/2
inline void insert(int L,int R,int x,int &y,int z){
y=++siz;
sum[y]=sum[x]+1;
if(L>=R) return ;
l[y]=l[x];r[y]=r[x];
if(z<=mid) insert(L,mid,l[x],l[y],z);
else insert(mid+1,R,r[x],r[y],z);
}
inline int query_sum(int L,int R,int x,int y,int left,int right){
int now=0;
if(y<x) return 0;
if(left<=L&&right>=R) return sum[y]-sum[x];
if(left<=mid) now+=query_sum(L,mid,l[x],l[y],left,right);
if(right>mid) now+=query_sum(mid+1,R,r[x],r[y],left,right);
return now;
}
inline int query_pre(int L,int R,int x,int y,int left,int right,int now){
if(L==R) return L;
if(sum[l[y]]-sum[l[x]]>=now) return query_pre(L,mid,l[x],l[y],left,right,now);
else return query_pre(mid+1,R,r[x],r[y],left,right,now-(sum[l[y]]-sum[l[x]]));
}
inline int query_sub(int L,int R,int x,int y,int left,int right,int now){
if(L==R) return L;
if(sum[r[y]]-sum[r[x]]>=now) return query_sub(mid+1,R,r[x],r[y],left,right,now);
else return query_sub(L,mid,l[x],l[y],left,right,now-(sum[r[y]]-sum[r[x]]));
}
int main(){
int i,now,left,right;
scanf("%d%d",&n,&m);
for(i=0;i<n;++i){
char ch=getchar();
while(ch<'a'||ch>'z') ch=getchar();
s[i]=ch-'a'+1;
}
build_sa();
build_height();
for(i=0;i<n;++i) insert(0,n,root[i],root[i+1],rank[i]);
while(m--){
int aa,bb,cc,dd;
aa=in();bb=in();cc=in();dd=in();
int L=0,R=bb-aa+1,maxn=0;
while(L<R){
now=query_sum(0,n,root[aa-1],root[bb-mid],0,rank[cc-1]-1);
left=now?query_pre(0,n,root[aa-1],root[bb-mid],0,rank[cc-1],now):-1;
now=query_sum(0,n,root[aa-1],root[bb-mid],rank[cc-1]+1,n);
right=now?query_sub(0,n,root[aa-1],root[bb-mid],rank[cc-1],n,now):-1;
int ans=0;
if(left!=-1){
int oo=min(LCP(left,rank[cc-1]),bb-sa[left]);
ans=max(ans,min(dd-cc+1,oo));
}
if(right!=-1){
int oo=min(LCP(rank[cc-1],right),bb-sa[right]);
ans=max(ans,min(dd-cc+1,oo));
}
if(cc>=aa&&cc<=bb) ans=max(ans,min(dd,bb)-cc+1);
maxn=max(maxn,ans);
if(ans>=mid) L=mid+1;
else R=mid;
}
printf("%d\n",maxn);
}
}