字符串
Description
佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CEO,嫁给高富帅,走上人生巅峰。每个问题均有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
想起「后缀数组」
后缀数组相关太久没写,已经几乎不记得了……
思路:
求解最长公共前缀相关问题通常会用到后缀数组。
于是从后缀数组的方向考虑。
首先,考虑一个可行的暴力:
枚举
[a,b]
中的节点
i
和
考虑如何优化这个暴力。
考虑二分答案。
对于每次二分的值
mid
,求出
height
数组中包含
sa[c]
这个位置的,最长的一段
min(h[x],h[y])≥mid
的区间
[x,y]
。
可以发现,这正是所有满足
lcp(c,i)≥mid
的合法的前缀的起点的集合。
也就是说,只有在
[x,y]
之中的起点才能满足与
c
的
那么也就是说,若
[a,b]
中有任何一个位置在
[x,y]
之中,则
[a,b]
至少有一个子串满足与
s[c,d]
的
lcp
答案大于等于
mid
。
这个位置的查询很显然可以直接上主席树~
然后就做完了,复杂度 O(nlog2n) 。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0;char ch=getchar();
while(ch<'0' || '9'<ch)ch=getchar();
while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
return x;
}
inline int minn(int a,int b){return a<b?a:b;}
typedef pair<int,int> pr;
const int N=100009;
const int K=23;
int n,m;
char s[N];
namespace st
{
int h[N][K],sa[N],rk[N];
int wc[N],wb[N],wa[N],bin[N];
inline void build(char *s,int n,int m,int *sa)
{
int *x=wb,*y=wa;
for(int i=1;i<=m;i++)
wc[i]=0;
for(int i=1;i<=n;i++)
++wc[x[i]=s[i]];
for(int i=1;i<=m;i++)
wc[i]+=wc[i-1];
for(int i=n;i>=1;i--)
sa[wc[x[i]]--]=i;
for(int j=1,p=0;j<=n && p<n;m=p,j<<=1,p=0)
{
for(int i=n-j+1;i<=n;i++)
y[++p]=i;
for(int i=1;i<=n;i++)
if(sa[i]>j)
y[++p]=sa[i]-j;
for(int i=1;i<=m;i++)
wc[i]=0;
for(int i=1;i<=n;i++)
wc[x[i]]++;
for(int i=1;i<=m;i++)
wc[i]+=wc[i-1];
for(int i=n;i>=1;i--)
sa[wc[x[y[i]]]--]=y[i];
swap(x,y);
x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
if(y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j])
x[sa[i]]=p;
else
x[sa[i]]=++p;
}
for(int i=1;i<=n;i++)
rk[sa[i]]=i;
for(int i=1,k=0;i<=n;i++)
{
if(k)k--;
int j=sa[rk[i]+1];
if(rk[i]==n)continue;
while(s[i+k]==s[j+k])
k++;
h[rk[i]][0]=k;
}
}
inline void init()
{
build(s,n,26,sa);
for(int i=2;i<=n;i++)
bin[i]=bin[i>>1]+1;
for(int i=1;i<=bin[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++)
{
if(h[j][i-1]<h[j+(1<<i-1)][i-1])
h[j][i]=h[j][i-1];
else h[j][i]=h[j+(1<<i-1)][i-1];
}
}
inline int query(int l,int r)
{
if(l>r)return 0;int dlt=bin[r-l+1];
return minn(h[l][dlt],h[r-(1<<dlt)+1][dlt]);
}
inline pr eq_range(int s,int t,int len)
{
int pos=rk[s],l,r,mid;
pr ret=pr(pos,pos);
l=pos+1;r=n;
while(l<=r)
{
mid=l+r>>1;
if(query(pos,mid-1)>=len)
l=mid+1,ret.second=mid;
else r=mid-1;
}
l=1;r=pos-1;
while(l<=r)
{
mid=l+r>>1;
if(query(mid,pos-1)>=len)
r=mid-1,ret.first=mid;
else l=mid+1;
}
return ret;
}
}
namespace pst
{
const int M=N*40;
int rt[N],t[M],ls[M],rs[M],tot;
inline int modify(int pre,int l,int r,int p)
{
int now=++tot;t[now]=t[pre]+1;
if(l==r)return now;int mid=l+r>>1;
if(p<=mid)ls[now]=modify(ls[pre],l,mid,p),rs[now]=rs[pre];
else ls[now]=ls[pre],rs[now]=modify(rs[pre],mid+1,r,p);
return now;
}
inline int query(int tl,int tr,int l,int r,int dl,int dr)
{
if(dl==l && r==dr)return t[tr]-t[tl];
int mid=l+r>>1;if(!(t[tr]-t[tl]))return 0;
if(dr<=mid)return query(ls[tl],ls[tr],l,mid,dl,dr);
if(mid<dl) return query(rs[tl],rs[tr],mid+1,r,dl,dr);
return query(ls[tl],ls[tr],l,mid,dl,mid)+query(rs[tl],rs[tr],mid+1,r,mid+1,dr);
}
inline void init()
{
for(int i=1;i<=n;i++)
rt[i]=modify(rt[i-1],1,n,st::sa[i]);
}
}
inline bool check(int a,int b,int c,int d,int len)
{
pr rg=st::eq_range(c,d,len);
if(rg.first>rg.second)return 0;
return pst::query(pst::rt[rg.first-1],pst::rt[rg.second],1,n,a,b)>0;
}
inline int query(int a,int b,int c,int d)
{
int l=1,r=minn(b-a+1,d-c+1),ans=0,mid;
while(l<=r)
{
mid=l+r>>1;
if(check(a,b-mid+1,c,d,mid))
l=mid+1,ans=mid;
else r=mid-1;
}
return ans;
}
int main()
{
n=read();m=read();
scanf("%s",s+1);
for(int i=1;i<=n;i++)
s[i]=s[i]-'a'+1;
st::init();
pst::init();
for(int i=1,a,b,c,d;i<=m;i++)
{
a=read();b=read();c=read();d=read();
printf("%lld\n",query(a,b,c,d));
}
return 0;
}