题意
给定串 s s s和 q q q个询问
每次问 s [ a . . . b ] s[a...b] s[a...b]的所有子串和 s [ c . . . . d ] s[c....d] s[c....d]的最长公共前缀最大值
考虑二分答案 m i d mid mid
问题转化为, s [ c . . . c + m i d − 1 ] s[c...c+mid-1] s[c...c+mid−1]是否在 s [ a . . . b ] s[a...b] s[a...b]的某个子串中出现过
考虑到 s [ c . . . c + m i d − 1 ] s[c...c+mid-1] s[c...c+mid−1]是前缀,而后缀自动机更擅长处理后缀,我们对s反转成 s ′ s' s′建 S A M SAM SAM
那么现在是 s ′ [ d − m i d + 1 , d ] s'[d-mid+1,d] s′[d−mid+1,d]是否在 s ′ [ a . . . b ] s'[a...b] s′[a...b]的某个子串中出现过
首先我们需要找到 s ′ [ d − m i d + 1 , d ] s'[d-mid+1,d] s′[d−mid+1,d]在 S A M SAM SAM上对应的节点,如何快速查找??
我们可以记录一下 s ′ [ 1... d ] s'[1...d] s′[1...d]在哪个节点,然后往倍增跳到 l o n g e s t > = m i d longest>=mid longest>=mid的节点 v v v
节点 v v v就包含了状态 s ′ [ d − m i d + 1 , d ] s'[d-mid+1,d] s′[d−mid+1,d]
那么现在就是判断 v v v的 e n d p o s endpos endpos中是否出现过 [ a + m i d − 1 , b ] [a+mid-1,b] [a+mid−1,b]即可,出现过,说明 s ′ [ a . . . b ] s'[a...b] s′[a...b]存在这个串
我们就找到了答案。
维护每个点的 e n d p o s endpos endpos就是对每个节点开一棵权值线段树,就从叶子节点一直往上线段树合并即可
稍微注意一下, a , b , c , d a,b,c,d a,b,c,d读进来要修改一下,因为现在是反串上操作
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
const int N = maxn*41;
int n,m;
int ls[N],rs[N],rt[N],sum[N],num;
void push_up(int root)
{
sum[root] = sum[ls[root]]+sum[rs[root]];
}
void add(int &root,int l,int r,int index)
{
if( l>index || r<index ) return;
if( !root ) root = ++num;
if( l==r&&l==index ){ sum[root] = 1; return; }
int mid = l+r>>1;
add( ls[root],l,mid,index); add( rs[root],mid+1,r,index);
push_up(root);
}
int ask(int &root,int l,int r,int L,int R)
{
if( l>R||r<L ) return 0;
if( !root ) return 0;
if( l>=L&&r<=R ) return sum[root];
int mid = l+r>>1;
return ask(ls[root],l,mid,L,R)+ask( rs[root],mid+1,r,L,R);
}
int merge(int x,int y,int l,int r)
{
if( !x || !y ) return x|y;
int p = ++num, mid = l+r>>1;
if( l==r ){sum[p] = sum[x]|sum[y]; return p; }
ls[p] = merge( ls[x],ls[y],l,mid);
rs[p] = merge( rs[x],rs[y],mid+1,r);
push_up(p);
return p;
}
int ed[maxn],zi[maxn][28],fa[maxn],len[maxn],las = 1, id = 1;
char s[maxn];
void insert(int c)
{
int p = las, np = ++id; las = id;
len[np] = len[p]+1;
add( rt[np],1,n,len[np] );//插入endpos
for( ; p && zi[p][c]==0 ; p = fa[p] ) zi[p][c] = np;
if( p==0 ) fa[np] = 1;
else
{
int q = zi[p][c];
if( len[q]==len[p]+1 ) fa[np] = q;
else
{
int nq = ++id;
memcpy( zi[nq],zi[q],sizeof zi[nq] );
len[nq] = len[p]+1; fa[nq] = fa[q];
fa[np] = fa[q] = nq;
for( ; p && zi[p][c]==q ;p = fa[p] ) zi[p][c] = nq;
}
}
}
vector<int>vec[maxn];
int f[maxn][22];//树上倍增
void dfs(int u,int father)
{
f[u][0] = father;
for(int i=1;i<=20;i++) f[u][i] = f[f[u][i-1]][i-1];
for( auto v:vec[u] )
{
dfs(v,u);
rt[u] = merge( rt[u],rt[v],1,n );
}
}
bool isok(int mid,int a,int b,int c,int d)
{
int p = ed[d];//[1...d]所在的节点
for(int i=20;i>=0;i--)
if( len[f[p][i]]>=mid ) p = f[p][i];//倍增,跳到[d-mid+1,d]所在的节点
if( ask( rt[p],1,n,a+mid-1,b) ) return true;//有endpos
return false;
}
int main()
{
cin >> n >> m >> ( s+1 );
reverse( s+1, s+1+n );
for(int i=1;i<=n;i++) insert( s[i]-'a' ),ed[i] = las;
for(int i=2;i<=id;i++) vec[fa[i]].push_back( i );
dfs(1,0);//线段树合并
for(int i=1;i<=m;i++)
{
int a,b,c,d; scanf("%d%d%d%d",&b,&a,&d,&c);
a = n-a+1, b = n-b+1, c = n-c+1, d = n-d+1;
int l = 1, r = d-c+1, ans = 0;
while( r>=l )
{
int mid = l+r>>1;
if( isok(mid,a,b,c,d) ) l = mid+1, ans = mid;
else r = mid-1;
}
printf("%d\n",ans);
}
}