字符串(tjoi2016,heoi2016,bzoj4556)(sam(后缀自动机)+线段树合并+倍增+二分答案)
佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了
一个长为\(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
题意:
中文题面,不解释
题解:
首先前缀不好处理,我们把字符串翻转。
然后发现很难直接求出答案,就采取二分最长后缀并验证。我们只需要验证左区间的后缀是否出现在右区间就行了。
然后用倍增在\(parent\)树上找到对应该子串的节点,并求出该节点能覆盖的节点(他子树中的节点),所以我们可以用线段树合并来求解。
#include<bits/stdc++.h>
#define mid (l+(r-l)/2)
using namespace std;
const int N=200010;
int lson[N*30],rson[N*30],sum[N*30];
int n,m,head[N],nxt[N],bian[N],p,q,np,nq,mp[N],pos[N],cnt,last;
int root[N],ch[N][27],fa[N],l[N],tot,sz,f[N][19],dep[N];
char s[N];
void ins(int x)
{
int c=s[x]-'a'+1;
p=last; np=++cnt; last=np; mp[np]=x; pos[x]=np;
l[np]=l[p]+1;
for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=1;
else {
q=ch[p][c];
if (l[p]+1==l[q]) fa[np]=q;
else {
nq=++cnt; l[nq]=l[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void add(int x,int y){
tot++,bian[tot]=y,nxt[tot]=head[x],head[x]=tot;
}
int updata(int pre,int l,int r,int x){
int rt=++sz; sum[rt]=sum[pre]+1;
if (l==r) return rt;
lson[rt]=lson[pre];
rson[rt]=rson[pre];
if (x<=mid) lson[rt]=updata(lson[pre],l,mid,x);
else rson[rt]=updata(rson[pre],mid+1,r,x);
return rt;
}
void pushup(int x){
int l=lson[x],r=rson[x];
sum[x]=sum[l]+sum[r];
}
int merge(int x,int y){
if(!x||!y)return x+y;
int rt=++sz;
lson[rt]=merge(lson[x],lson[y]);
rson[rt]=merge(rson[x],rson[y]);
pushup(rt);
return rt;
}
void dfs(int x){
for (int i=1;i<=17;i++){
if (dep[x]-(1<<i)<0) break;
f[x][i]=f[f[x][i-1]][i-1];
}
for (int i=head[x];i;i=nxt[i]){
f[bian[i]][0]=x,dep[bian[i]]=dep[x]+1;
dfs(bian[i]);
root[x]=merge(root[x],root[bian[i]]);
}
}
int query(int rt,int l,int r,int x,int y){
if(x<=l&&r<=y)return sum[rt];
int ans=0;
if(x<=mid)ans+=query(lson[rt],l,mid,x,y);
if(y>mid)ans+=query(rson[rt],mid+1,r,x,y);
return ans;
}
bool check(int k,int left,int right,int x){
if(k==0)return 1;
for(int rt=17;rt>=0;rt--)
if(l[f[x][rt]]>=k)x=f[x][rt];
return query(root[x],1,n,left,right);
}
int main(){
cin>>n>>m;
cin>>s+1;reverse(s+1,s+n+1);
last=++cnt;
for(int i=1;i<=n;++i)ins(i);
for(int i=1;i<=cnt;++i)add(fa[i],i);
for(int i=1;i<=cnt;++i)
if(mp[i])root[i]=updata(root[i],1,n,mp[i]);
dep[1]=1; dfs(1);
for (int i=1;i<=m;++i){
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
swap(a,b); swap(c,d);
a=n-a+1; b=n-b+1; c=n-c+1; d=n-d+1;
int l=0,r=min(d-c+1,b-a+1),ans=0;
while (l<=r){
if (check(mid,a+mid-1,b,pos[d])) ans=max(ans,mid),l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
}