【BZOJ4556】字符串(TJOI&HEOI2016)-后缀数组+二分+RMQ+主席树
测试地址:字符串
做法:本题需要用到后缀数组+二分+RMQ+主席树。
注意到要求的每个子串和的LCP最大值,其实就是求的每个后缀和的LCP最大值。要求LCP我们通常要先对字符串求出后缀数组,然后在上做RMQ,可以做到询问。我们知道的一个后缀与的LCP长度为:,其中指以第个字符开头的后缀和以第个字符开头的后缀的LCP。这个东西和的取值有关,所以我们不好直接求,怎么办呢?
观察到一个性质,如果长为的LCP存在,那么长为的LCP显然也存在,这个性质是单调的,因此我们二分答案,问题转化为判定性问题:存不存在长为的LCP?那么首先,的一个后缀要满足,的取值范围应该是区间,所以问题就变成,求以第个到第个字符开头的这些后缀中,存不存在一个后缀与以第个字符开头的后缀的LCP长度。
我们又发现,与第个字符开头的后缀的LCP长度的那些后缀,在后缀数组上是一个连续的区间,且我们可以二分找到这个区间,二分的复杂度是的,二分时要求两个后缀的LCP,我们前面已经说了能用RMQ做到询问,所以可以接受。
于是问题就变成给定一个序列(即),问其中的某个区间中存不存在一个数在某一个权值区间内。这是非常经典的主席树的应用,于是我们在主席树上就可以做到查询。那么我们就解决了这一题,总的时间复杂度为。
(本来以为这题这么复杂要调很久,没想到1A了,信心up)
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,tot=0,rt[100010],ch[2000010][2]={0},sum[2000010]={0};
int x[100010],y[100010],cnt[100010],s[100010];
int SA[100010],Rank[100010],height[100010];
int mn[100010][20],len[100010];
char S[100010];
void calc_SA()
{
int p=1,m=26;
for(int i=1;i<=n;i++)
x[i]=S[i]-'a'+1;
while(p<n)
{
for(int i=1;i<=n;i++)
{
if (i+p<=n) y[i]=x[i+p];
else y[i]=0;
}
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++) cnt[y[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=1;i<=n;i++) s[cnt[y[i]]--]=i;
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++) cnt[x[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) SA[cnt[x[s[i]]]--]=s[i];
m=0;
for(int i=1;i<=n;i++)
{
if (i==1||x[SA[i]]!=x[SA[i-1]]||y[SA[i]]!=y[SA[i-1]])
m++;
Rank[SA[i]]=m;
}
if (m==n) break;
for(int i=1;i<=n;i++)
x[i]=Rank[i];
p<<=1;
}
}
void calc_height()
{
int last=0;
for(int i=1;i<=n;i++)
{
if (Rank[i]==n)
{
height[Rank[i]]=last=0;
continue;
}
while(S[i+last]==S[SA[Rank[i]+1]+last]) last++;
height[Rank[i]]=last;
last=max(last-1,0);
}
}
void pre_rmq()
{
int x=0;
for(int i=1;i<n;i++)
{
if ((1<<(x+1))<i) x++;
len[i]=x;
}
for(int i=1;i<n;i++)
mn[i][0]=height[i];
for(int i=1;i<=17;i++)
for(int j=1;j+(1<<(i-1))<n;j++)
mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);
}
int LCP(int l,int r)
{
r--;
if (l>r) return l;
int p=len[r-l+1];
return min(mn[l][p],mn[r-(1<<p)+1][p]);
}
void buildtree(int &v,int l,int r)
{
v=++tot;
if (l==r) return;
int mid=(l+r)>>1;
buildtree(ch[v][0],l,mid);
buildtree(ch[v][1],mid+1,r);
}
void insert(int &v,int last,int l,int r,int x)
{
v=++tot;
sum[v]=sum[last];
ch[v][0]=ch[last][0];
ch[v][1]=ch[last][1];
if (l==r) {sum[v]++;return;}
int mid=(l+r)>>1;
if (x<=mid) insert(ch[v][0],ch[last][0],l,mid,x);
else insert(ch[v][1],ch[last][1],mid+1,r,x);
sum[v]=sum[ch[v][0]]+sum[ch[v][1]];
}
bool query(int last,int v,int l,int r,int s,int t)
{
if (sum[v]-sum[last]==0) return 0;
if (l>=s&&r<=t) return 1;
int mid=(l+r)>>1;
if (s<=mid&&query(ch[last][0],ch[v][0],l,mid,s,t)) return 1;
if (t>mid&&query(ch[last][1],ch[v][1],mid+1,r,s,t)) return 1;
return 0;
}
bool check(int x,int a,int b,int c)
{
int l,r,L,R;
c=Rank[c];
l=1,r=c;
while(l<r)
{
int mid=(l+r)>>1;
if (LCP(mid,c)>=x) r=mid;
else l=mid+1;
}
L=l;
l=c,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if (LCP(c,mid+1)>=x) l=mid+1;
else r=mid;
}
R=l;
return query(rt[a-1],rt[b-x+1],1,n,L,R);
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",S+1);
calc_SA();
calc_height();
pre_rmq();
buildtree(rt[0],1,n);
for(int i=1;i<=n;i++)
insert(rt[i],rt[i-1],1,n,Rank[i]);
for(int i=1;i<=m;i++)
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
int l=0,r=min(b-a+1,d-c+1);
while(l<r)
{
int mid=(l+r)>>1;
if (check(mid+1,a,b,c)) l=mid+1;
else r=mid;
}
printf("%d\n",l);
}
return 0;
}