【LuoguP4482】[BJWC2018]Border 的四种求法
题意
区间 boder
\(n,q\leq 2*10^5\)
Sol
(暴力哈希/SA可以水过)
字符串区间询问问题,考虑用 \(SAM\) 解决。
boder相当于是询问区间 \([l,r]\) 内满足 \(lcs(i,r)>=i-l+1\) 的最大的 \(i\)
那么首先可以得到一个暴力做法,我们定位 \([1,r]\) 这个串的节点,那么相当于要询问的就是它的祖先。每一个点可以直接把它的 \(len\) 值视为 \(lcs(i,r)\)。这样的话用线段树合并维护出endpos后一个个往上跳就可以用线段树进行询问了,每次找到 \([l,min(l+len-1,r-1)]\) 里的最大的 \(endpos\) 用来更新答案。
就像 \(boder\) 不具有可二分性一样,上面的做法也没有什么优化空间。
这样我们肯定是从离线询问简化查询入手。
我们不能一个个跳 \(parent\) 树那样复杂度必然不对。
考虑到对于一个询问 \([l,r]\) 我们要用的其实是祖先节点的 \(endpos\) 中 \(i-len+1<=l\) 的且在询问区间内的部分。
于是我们想可不可以把祖先的 \(endpos\) 丢在一起查询从而避免一个个往上跳。
方法自然是有的,首先我们发现和询问同一个子树的 \(endpos\) 是没有用的,因为这个 \(endpos\) 在下面一定不会更差。
那么我们就可以想到这样一个方法:
把询问挂在树上后,从上面下来,用线段树维护好外面所有点的 \(i-len+1\),然后到一个点的时候就可以直接用线段树查询是否有合法节点了。
直接这样做显然复杂度爆炸,主要问题在于如果一个点的子树过多,那么一个点被加入删除的次数就爆炸了。
这时我们就可以用上 dsu on tree 来优化了。
本质上其实是轻重链剖分分治。
我们必须要让一个点被加入线段树的次数得到控制。发现一个点会在它的每一个祖先处被加入/删除一次,这个就可以用树链剖分来优化到 \(log\) 次。
我们把询问在每跳一次轻边后都挂在当前点上,然后对整棵树 dfs ,每次处理完重链上的所有轻子树后,从重链顶端往下一次加入轻子树的所有节点,这样到了一个点我们保存的就是除了这个点的子树外的所有子树。而每一个点只会在轻重边切换的时候被加入一次,总共只有 \(log\) 次。
但是这样还有一个问题,当我们的询问跳了一条重链后,到达的点的其他子树也可能贡献答案。
但是我们这样子就会把重儿子也给加进去,那样就凉了,复杂度会挂。
所以为了避免这种情况,我们先在这种节点处用暴力线段树合并的方法直接处理对答案的贡献。
由于询问个数只是多乘一个 \(log\) 总复杂度不变。
时间复杂度为 \(O(nlog^2n)\)
code:
#include<bits/stdc++.h>
using namespace std;
#define Set(a,b) memset(a,b,sizeof(a))
template<class T>inline void init(T&x){
x=0;char ch=getchar();bool t=0;
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
if(t) x=-x;return;
}typedef long long ll;
const int N=2e5+10;
const int MAXN=5e5;
const int MAXM=5e6;
char s[N];
int q,l,r;
int n,node=0,lst=0;
int son[MAXN][26];
int fa[MAXN],len[MAXN],aim[N],size[MAXN],top[MAXN],Son[MAXN],ans[N];
struct edge{int to,next;}a[MAXM];
int head[MAXN],cnt=0,QL[N],QR[N];
inline void add(int x,int y){a[++cnt]=(edge){y,head[x]};head[x]=cnt;}
vector<int> Que[MAXN];
namespace Endpos{
int ls[MAXM],rs[MAXM],Mn[MAXM];int node=0;int rt[MAXN];
inline int chk(int x,int y){if(!x) return y;if(!y) return x;return min(x,y);}
inline void Insert(int&u,int l,int r,int p){
if(!u) u=++node;Mn[u]=chk(Mn[u],p);
if(l==r) return;
int mid=(l+r)>>1;
if(mid>=p) Insert(ls[u],l,mid,p);
else Insert(rs[u],mid+1,r,p);
return;
}
int Merge(int u,int v,int l,int r){
if(!u||!v) return u|v;Mn[u]=chk(Mn[u],Mn[v]);
if(l==r) return u;int mid=(l+r)>>1;
ls[u]=Merge(ls[u],ls[v],l,mid);
rs[u]=Merge(rs[u],rs[v],mid+1,r);
return u;
}
int Query(int u,int l,int r,int L,int R){
if(!u||L>R) return 0;int mid=(l+r)>>1;
if(l==r) return l;
if(L> mid) return Query(rs[u],mid+1,r,L,R);
if(R<=mid) return Query(ls[u],l,mid,L,R);
if(rs[u]&&Mn[rs[u]]<=R) return Query(rs[u],mid+1,r,mid+1,R);
return Query(ls[u],l,mid,L,mid);
}
void dfs(int u){
for(int v,i=head[u];i;i=a[i].next) {
v=a[i].to;dfs(v);
rt[u]=Merge(rt[u],rt[v],1,n);
}
for(int id:Que[u]) {// 重链底部
int l=QL[id],r=QR[id];// i-l+1 <= len[u] => i<= len[u]+l-1
ans[id]=max(ans[id],Query(rt[u],1,n,l,min(r-1,len[u]+l-1))-l+1);
}return;
}
}
int sta[MAXN];
inline void extend(int c){
int u=lst;int p=lst=++node;len[p]=len[u]+1,aim[len[p]]=p;sta[p]=len[p];
Endpos::Insert(Endpos::rt[p],1,n,len[p]);
while(~u&&!son[u][c]) son[u][c]=p,u=fa[u];
if(~u) {
int v=son[u][c];
if(len[v]==len[u]+1) return void(fa[p]=v);
int q=++node;memcpy(son[q],son[v],sizeof(son[v]));
fa[q]=fa[v];len[q]=len[u]+1;fa[v]=fa[p]=q;
while(~u&&son[u][c]==v) son[u][c]=q,u=fa[u];
}return;
}
void dfs1(int u){
size[u]=1;
for(int v,i=head[u];i;i=a[i].next) {
v=a[i].to;dfs1(v);size[u]+=size[v];
if(!Son[u]||size[Son[u]]<size[v]) Son[u]=v;
}return;
}
void dfs2(int u,int tp){
top[u]=tp;if(!Son[u]) return;
dfs2(Son[u],tp);
for(int v,i=head[u];i;i=a[i].next) {
v=a[i].to;if(v==Son[u]) continue;
dfs2(v,v);
}return;
}
inline void Deal(int i){
int p=aim[QR[i]];
while(p>0) Que[p].emplace_back(i),p=fa[top[p]];
return;
}
namespace Chain{//i-l+1 <= len[u] => i-len[u]+1 <= l
const int MAX=2e6;
int Mn[MAX],ls[MAX],rs[MAX];int node=0;
int rt=0;
inline void Insert(int&u,int l,int r,int p,int x){
if(!u) {u=++node;ls[u]=rs[u]=0;Mn[u]=1e9;}
Mn[u]=min(Mn[u],x);
if(l==r) return;
int mid=(l+r)>>1;
if(mid>=p) Insert(ls[u],l,mid,p,x);
else Insert(rs[u],mid+1,r,p,x);
return;
}
void Pushin(int u,int len){
if(sta[u]) Insert(rt,1,n,sta[u],sta[u]-len+1);
for(int v,i=head[u];i;i=a[i].next) {v=a[i].to;Pushin(v,len);}
return;
}
int Query(int u,int l,int r,int L,int R,int x){
if(!u||L>R||Mn[u]>x) return 0;
if(l==r) return l;
int mid=(l+r)>>1;
if(mid>=R) return Query(ls[u],l,mid,L,R,x);
if(mid< L) return Query(rs[u],mid+1,r,L,R,x);
int ret=0;
if(rs[u]&&Mn[rs[u]]<=x) ret=Query(rs[u],mid+1,r,mid+1,R,x);
if(ret) return ret;return Query(ls[u],l,mid,L,mid,x);
}
void dfs(int u) {
for(int now=u;Son[now];now=Son[now]) {
for(int v,i=head[now];i;i=a[i].next) {v=a[i].to;if(v==Son[now]) continue;dfs(v);}
}rt=node=0;int now=u;
do{
for(int v,i=head[now];i;i=a[i].next) {v=a[i].to;if(v==Son[now]) continue;Pushin(v,len[now]);}
if(sta[now]) Insert(rt,1,n,sta[now],sta[now]-len[now]+1);
for(int id:Que[now]) {ans[id]=max(ans[id],Query(rt,1,n,QL[id],QR[id]-1,QL[id])-QL[id]+1);}
now=Son[now];
}while(now);return;
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);init(q);fa[0]=-1;lst=0;
for(int i=1;i<=n;++i) extend(s[i]-'a');
for(int i=1;i<=node;++i) add(fa[i],i);
dfs1(0),dfs2(0,0);
for(int i=1;i<=q;++i) {init(QL[i]),init(QR[i]);Deal(i);}
Endpos::dfs(0);Chain::dfs(0);
for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}