题目描述
佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CEO,嫁给高富帅,走上人生巅峰。每个问题均有a,b,c,d四个参数,问你子串s[a…b]的所有子串和s[c…d]的最长公共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?
输入输出格式
输入格式:
输入的第一行有两个正整数
,分别表示字符串的长度和询问的个数。接下来一行是一个长为
的字符串。接下来
行,每行有4个数
,表示询问
的所有子串和
的最长公共前缀的最大值。
输出格式:
对于每一次询问,输出答案。
输入输出样例
输入样例#1:
5 5
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4
输出样例#1:
1
1
2
2
2
说明
对于10%的数据,
对于40%的数据, ,字符串中仅有
对于100%的数据, ,字符串中仅有小写英文字母, \
分析:
设
,显然只有
的后缀才对答案有贡献。我们先对
建一个后缀数组,相当于求与以
开头的后缀的与
的后缀的
最大值(大概可以这样理解,虽然有边界问题)。
考虑二分一个答案,显然这个答案的上限是
,对于一个答案
,可以二分出与
这个后缀的
大于等于
的区间。显然后缀排序后,一个后缀与其他后缀的
是一个单峰函数。
加入二分出来的区间为
,而
串中只有前
个后缀长度超过
,相当于查询在排名为
到
的后缀中,有没有一个后缀是原串的第
位。我们可以以排名为下标建可持久化权值线段
树即可。
要先预处理
,因为c++自带的求
采用的是二分,复杂度是
的。
所以总复杂度是
级别的。
代码:
// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
const int maxn=1e5+7;
using namespace std;
int n,m,cnt;
int x[maxn],y[maxn],c[maxn],root[maxn],Log2[maxn];
char s[maxn];
struct node{
int l,r,data;
}t[maxn*31];
struct suffix_array{
int h[maxn][20],rank[maxn],sa[maxn];
void getsa()
{
int m=1000;
for (int i=1;i<=m;i++) c[i]=0;
for (int i=1;i<=n;i++) x[i]=s[i];
for (int i=1;i<=n;i++) c[x[i]]++;
for (int i=1;i<=m;i++) c[i]+=c[i-1];
for (int i=n;i>0;i--) sa[c[x[i]]--]=i;
for (int k=1;k<=n;k<<=1)
{
int num=0;
for (int i=n-k+1;i<=n;i++) y[++num]=i;
for (int i=1;i<=n;i++) if (sa[i]>k) y[++num]=sa[i]-k;
for (int i=1;i<=m;i++) c[i]=0;
for (int i=1;i<=n;i++) c[x[i]]++;
for (int i=1;i<=m;i++) c[i]+=c[i-1];
for (int i=n;i>0;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
num=1;
x[sa[1]]=1;
for (int i=2;i<=n;i++)
{
if ((y[sa[i]]!=y[sa[i-1]]) || (y[sa[i]+k]!=y[sa[i-1]+k]))
{
x[sa[i]]=++num;
}
else x[sa[i]]=num;
}
if (num>=n) break;
m=num;
}
for (int i=1;i<=n;i++) rank[i]=x[i];
}
void getheight()
{
int k=0;
for (int i=1;i<=n;i++)
{
if (k) k--;
int j=sa[rank[i]-1];
while ((i+k<=n) && (j+k<=n) && (s[i+k]==s[j+k])) k++;
h[rank[i]][0]=k;
}
int c=1;
for (int j=1;j<20;j++)
{
for (int i=1;i<=n;i++)
{
if (i+c>n) h[i][j]=h[i][j-1];
else h[i][j]=min(h[i][j-1],h[i+c][j-1]);
}
c<<=1;
}
}
int lcp(int x,int y)
{
x=rank[x],y=rank[y];
if (x>y) swap(x,y);
x++;
int k=Log2[y-x+1];
return min(h[x][k],h[y-(1<<k)+1][k]);
}
}a;
void ins(int &p,int q,int l,int r,int x)
{
if (!p) p=++cnt;
t[p].data=t[q].data+1;
if (l==r) return;
int mid=(l+r)/2;
if (x<=mid) t[p].r=t[q].r,ins(t[p].l,t[q].l,l,mid,x);
else t[p].l=t[q].l,ins(t[p].r,t[q].r,mid+1,r,x);
}
int query(int p,int q,int l,int r,int x,int y)
{
if ((l==x) && (r==y)) return t[p].data-t[q].data;
if (t[p].data-t[q].data==0) return 0;
int mid=(l+r)/2;
if (y<=mid) return query(t[p].l,t[q].l,l,mid,x,y);
else if (x>mid) return query(t[p].r,t[q].r,mid+1,r,x,y);
else return query(t[p].l,t[q].l,l,mid,x,mid)+query(t[p].r,t[q].r,mid+1,r,mid+1,y);
}
int find1(int d,int x)
{
int l=1,r=x-1,ans=x;
while (l<=r)
{
int mid=(l+r)/2;
if (a.lcp(a.sa[mid],a.sa[x])>=d) r=mid-1,ans=mid;
else l=mid+1;
}
return ans;
}
int find2(int d,int x)
{
int l=x+1,r=n,ans=x;
while (l<=r)
{
int mid=(l+r)/2;
if (a.lcp(a.sa[x],a.sa[mid])>=d) l=mid+1,ans=mid;
else r=mid-1;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
a.getsa();
a.getheight();
for (int i=1;i<=n;i++)
{
ins(root[i],root[i-1],1,n,a.sa[i]);
}
for (int i=1;i<=n;i++) Log2[i]=trunc(log(i+0.5)/log(2));
for (int i=1;i<=m;i++)
{
int x,y,A,B;
scanf("%d%d%d%d",&x,&y,&A,&B);
int l=1,r=min(B-A+1,y-x+1),ans=0;
while (l<=r)
{
int mid=(l+r)/2;
int lc=find1(mid,a.rank[A]),rc=find2(mid,a.rank[A]);
if (query(root[rc],root[lc-1],1,n,x,y-mid+1)) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
}