[洛谷P6640] BJOI2020 封印
题目链接
解析
我们先给 \(T\) 串建一个后缀自动机,然后求出 \(S\) 中以每一个位置结尾的最长公共子串(设其长度为 \(len_i\))。这样问题就转变为对于区间 \([L,R]\) 求最大的 \(min(len_i,i-L+1)\) 。发现这个 \(min\) 实在是不太好搞,我们转化一下,如果 \(len_i\le i-L+1\) 就可以得到 \(i-len_i+1\ge L\) 。而 \(i-len_i+1\) 是随着 \(i\) 的增大而不降的。因此我们可以通过二分找到一个位置 \(pos\) 使得 \([pos,R]\) 中的 \(i-len_i+1\) 全都不小于 \(L\) 。二分和统计答案的过程可以用线段树维护。
当然正确的写法是ST表,但是有O2
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 200002
using namespace std;
struct SAM{
int len,link,son[2];
}a[N*2];
struct SegmentTree{
int dat,len;
}t[N*4];
char S[N],T[N];
int n,m,q,i,val[N],cnt,last;
void extend(char c)
{
int cur=++cnt,now=last,x=(int)(c-'a');
a[cur].len=a[now].len+1;
while(now!=-1&&!a[now].son[x]){
a[now].son[x]=cur;
now=a[now].link;
}
if(now==-1) a[cur].link=0;
else{
int p=now,q=a[p].son[x];
if(a[q].len==a[p].len+1) a[cur].link=q;
else{
int tmp=++cnt;
a[tmp]=a[q];
a[tmp].len=a[p].len+1;
a[q].link=a[cur].link=tmp;
while(p!=-1&&a[p].son[x]==q){
a[p].son[x]=tmp;
p=a[p].link;
}
}
}
last=cur;
}
void build(int p,int l,int r)
{
if(l==r){
t[p].len=val[l];
t[p].dat=l-val[l]+1;
return;
}
int mid=(l+r)/2;
build(p*2,l,mid);build(p*2+1,mid+1,r);
t[p].len=max(t[p*2].len,t[p*2+1].len);
t[p].dat=min(t[p*2].dat,t[p*2+1].dat);
}
int ask1(int p,int l,int r,int ql,int qr)
{
if(ql>qr) return 0;
if(ql<=l&&r<=qr) return t[p].len;
int mid=(l+r)/2,ans=0;
if(ql<=mid) ans=max(ans,ask1(p*2,l,mid,ql,qr));
if(qr>mid) ans=max(ans,ask1(p*2+1,mid+1,r,ql,qr));
return ans;
}
int ask2(int p,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr) return t[p].dat;
int mid=(l+r)/2,ans=1<<30;
if(ql<=mid) ans=min(ans,ask2(p*2,l,mid,ql,qr));
if(qr>mid) ans=min(ans,ask2(p*2+1,mid+1,r,ql,qr));
return ans;
}
int main()
{
scanf("%s%s",S+1,T+1);
n=strlen(S+1);m=strlen(T+1);
a[0].link=-1;
for(i=1;i<=m;i++) extend(T[i]);
int v=0,l=0;
for(i=1;i<=n;i++){
int p=(int)(S[i]-'a');
while(v&&!a[v].son[p]) v=a[v].link,l=a[v].len;
if(a[v].son[p]) v=a[v].son[p],l++;
val[i]=l;
}
build(1,1,n);
scanf("%d",&q);
for(i=1;i<=q;i++){
int L,R;
scanf("%d%d",&L,&R);
int l=L,r=R,mid,pos=R+1;
while(l<=r){
mid=(l+r)/2;
if(ask2(1,1,n,mid,R)>=L) pos=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",max(pos-L,ask1(1,1,n,pos,R)));
}
return 0;
}