[HEOI2016/TJOI2016]字符串(后缀自动机,可持久化线段树,线段树合并,二分答案)
[HEOI2016/TJOI2016]字符串
给出一个长度为\(n\)的字符串\(s\),和\(m\)个问题。
每个问题均有\(a,b,c,d\)四个参数,问你子串\(s[a,b]\)的所有子串和\(s[c,d]\)的最长公共前缀的长度的最大值是多少?
做法:
二分一个答案\(mid\)。
现在我们要判断\(s[c,c+mid-1]\)是否在\(s[a,b]\)出现过。
首先找到\(s[c,c+mid-1]\)所在的状态:
建出link树,从\(s[1,c+mid-1]\)的节点倍增向上跳到最后一个\(len>=mid\)的节点。根据后缀自动机存储所有子串的性质,这个节点表示的子串一定有一个长度为\(len\)。
记这个节点为\(now\)。
现在我们要判断\(now\)的\(endpos\)集合中是否含有\([a+mid-1,b]\)中的某个数,我们给每个节点开一个权值线段树用来维护该节点的\(endpos\)位置,然后自底向上合并线段树即可。这里需要可持久化的合并,因为每个线段树要被重复使用。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
const int M=maxn*40;
int len[maxn],link[maxn],nxt[maxn][26],tot=1,lst=1;
string s;
void sam_extend (char c) {
int cur=++tot;
len[cur]=len[lst]+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 n,m,p[maxn],father[25][maxn],T[maxn],lson[M],rson[M],c[M],tol;
int up (int u,int l,int r,int p,int v) {
if (!u) u=++tol;
if (l==r) {
c[u]+=v;
return u;
}
int mid=(l+r)>>1;
if (p<=mid) lson[u]=up(lson[u],l,mid,p,v);
if (p>mid) rson[u]=up(rson[u],mid+1,r,p,v);
c[u]=c[lson[u]]+c[rson[u]];
return u;
}
int merge (int x,int y,int l,int r) {
if (!x||!y) return x+y;
int u=++tol;
c[u]=c[x]+c[y];
if (l==r) return u;
int mid=(l+r)>>1;
lson[u]=merge(lson[x],lson[y],l,mid);
rson[u]=merge(rson[x],rson[y],mid+1,r);
return u;
}
int query (int u,int l,int r,int L,int R) {
if (L>R) return 0;
if (l>=L&&r<=R) return c[u];
int mid=(l+r)>>1;
int ans=0;
if (L<=mid) ans+=query(lson[u],l,mid,L,R);
if (R>mid) ans+=query(rson[u],mid+1,r,L,R);
return ans;
}
void dfs (int u) {
for (int v:g[u]) {
father[0][v]=u;
dfs(v);
T[u]=merge(T[u],T[v],1,n);
}
}
bool check (int mid,int a,int b,int c,int d) {
int u=p[c+mid-1];
for (int i=20;i>=0;i--) {
if (father[i][u]&&len[father[i][u]]>=mid) u=father[i][u];
}
return query(T[u],1,n,a+mid-1,b)>0;
}
int main () {
ios::sync_with_stdio(false);
cin>>n>>m;
cin>>s;
p[0]=1;
for (int i=1;i<=n;i++) {
sam_extend(s[i-1]);
p[i]=lst;
T[lst]=up(T[lst],1,n,i,1);
}
for (int i=2;i<=tot;i++) g[link[i]].push_back(i);
dfs(1);
for (int i=1;i<=20;i++) for (int j=1;j<=tot;j++) father[i][j]=father[i-1][father[i-1][j]];
while (m--) {
int a,b,c,d;
cin>>a>>b>>c>>d;
int l=0,r=min(b-a+1,d-c+1);
int ans=0;
while (l<=r) {
int mid=(l+r)>>1;
if (check(mid,a,b,c,d)) {
ans=mid;
l=mid+1;
}
else {
r=mid-1;
}
}
cout<<ans<<'\n';
}
}