P6640 [BJOI2020] 封印(后缀自动机+线段树+二分)
首先对每个位置处理出它作为右端点的最长子序列zz[i]
这个问题是老题
然后,每次区间询问就是求
max(i-max(l,i-zz[i]+1)+1)
这里注意到,i-zz[i]+1这玩意好像是单调不减的?
尝试证一下...
那么可以二分区间内第一个i-zz[i]+1大于l的位置p
p前面半段的答案稳定
p后面半段的答案用线段树维护zz[i]的最大值即可
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6;
int len[maxn],link[maxn],nxt[maxn][26];
int sz[maxn];
int tot=1,lst=1;
string s,t;
int n;
void sam_extend (char c) {
int cur=++tot;
len[cur]=len[lst]+1;
sz[cur]=1;
int p=lst;
while (p&&!nxt[p][c-'a']) {
nxt[p][c-'a']=cur;
p=link[p];
}
if (!p) {
link[cur]=1;
}
else {
int q=nxt[p][c-'a'];
if (len[p]+1==len[q]) {
link[cur]=q;
}
else {
int clone=++tot;
len[clone]=len[p]+1;
for (int i=0;i<26;i++) {
nxt[clone][i]=nxt[q][i];
}
link[clone]=link[q];
while (p&&nxt[p][c-'a']==q) {
nxt[p][c-'a']=clone;
p=link[p];
}
link[q]=link[cur]=clone;
}
}
lst=cur;
}
vector<int> g[maxn];
int z[maxn];
int zz[maxn];
int c[maxn];
void build (int i,int l,int r) {
if (l==r) {
c[i]=z[l];
return;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
c[i]=max(c[i<<1],c[i<<1|1]);
}
int query (int i,int l,int r,int L,int R) {
if (l>=L&&r<=R) return c[i];
int mid=(l+r)>>1;
int ans=0;
if (L<=mid) ans=max(ans,query(i<<1,l,mid,L,R));
if (R>mid) ans=max(ans,query(i<<1|1,mid+1,r,L,R));
return ans;
}
int main () {
cin>>s>>t;
n=s.size();
for (char i:t) {
sam_extend(i);
}
int v=1,l=0;
for (int i=0;i<s.size();i++) {
while (v&&!nxt[v][s[i]-'a']) {
v=link[v];
l=len[v];
}
if (nxt[v][s[i]-'a']) {
v=nxt[v][s[i]-'a'];
l++;
}
z[i+1]=l;
zz[i+1]=i+1-l+1;
}
//for (int i=1;i<=n;i++) printf("%d ",z[i]);
build(1,1,n);
int q;
cin>>q;
while (q--) {
int l,r;
scanf("%d%d",&l,&r);
int L=l,R=r,p=-1;
while (L<=R) {
int mid=(L+R)>>1;
if (zz[mid]>l) {
p=mid;
R=mid-1;
}
else {
L=mid+1;
}
}
int ans;
//printf("%d ",p);
if (p!=-1)
ans=max(p-l,query(1,1,n,p,r));
else
ans=r-l+1;
printf("%d\n",ans);
}
}