题解[P7361拜神]
题意:给定一个字符串,每次询问一段区间中出现两次的最长子串长度。
思路还较清新,先将询问按右端点 \(r\) 排序,看对每个询问 \(l\) 能怎么方便求出。
将每个 \([1,r]\) 内的子串 \(p\) ,其在 \([1,r]\) 中次后出现的位置的右端点与左端点看做一条线段 \([s_p,t_p]\)。
那对每个 \(l\) 只需查出 \(\mathrm{Max}\left\{\left(\mathop{\mathrm{Max}}\limits_{s_p\leq l\leq t_p}t_p-l+1\right),\left(\mathop{\mathrm{Max}}\limits_{s_p\geq l}\ t_p-s_p+1\right)\right\}\)
这相当于是找出覆盖 \(l\) 的线段中的右端点最大值 \(-l\) ,与及不覆盖 \(l\) 且在 \(l\) 右侧的线段长度最大值的最大值。
由于对于 \(\text{endpos}\) 相同的子串右端点集合相同,且上式需要的线段是越长越好。
于是我们需要的线段仅为 \(\text{endpos}\) 相同的串中最长的一个,先想如何更新次右端点。
联系一下这题,不难得出 \(\text{LCT}\) 维护的方法。
就是建出 \(\text{parent tree}\) 后记录每个等价类出现位置的最右位置,以及次右位置。
\(r\) 向右移时单次 \(access\) 更改为了求上式需维护的信息,同时打上将次右值变成最右值,最右值更新为 \(r\) 的标记。
剩下的就是考虑上式怎么求出。
首先第二个 \(\mathrm{Max}\) 比较好做,因为每个子串的 \(s_p\) 单调递增,可以直接将 \([1,s_p]\) 的信息与 \(t_p-s_p+1\) 取最大值。
对于第一个 \(\mathrm{Max}\) 先考虑没有 \(access\) 顺带需要的修改该怎么做。
这可以将每个在 \([s_p,t_p]\) 之间的点,用 \(t_p\) 更新能覆盖这个点的线段最大右端点。
观察到每次更改所影响到的线段可视为向右平移一段区间,事实上之前的区间取最大值并不会影响之后的查询。
这是因为之后查询的 \(l\) 如果在原先 \(t_p\) 之后,就不会影响答案。
而在原先 \(t_p\) 之前的 \(l\) 必定会查到右移之后的的整个字符串,或者之后的右端点比之前右端点靠右。这两种情况分别对应右移前后,是否与原先字符串重合。而这两种情况一定会在第一、二个 \(\mathrm{Max}\) 中得出的长度比之前优,会将之前的标记覆盖掉。
于是只需要一个支持区间取 \(\mathrm{Max}\) 的数据结构,用线段树容易实现。
最终时间复杂度是 \(O(n\log^2n)\) 的。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m,x,xx,y,tot=1,last=1,tmp,l,r;char ch;
int rot_xx,rot_y,s_i,acs_y,a;bool rot_b;
int mxlen[N],link[N],trans[N][26],f[N],res[N];
int lst[N],lst_[N],son[N][2],anc[N],cov[N];
inline void read(int &x){
x=0;ch=getchar();while(ch<48||ch>57)ch=getchar();
while(ch>47&&ch<58)x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
}
void write(int x){if(x>9)write(x/10);putchar(48+x%10);}
#define ls k<<1
#define rs k<<1|1
struct segment_tree{
int tag[N<<2],res;//这里用了标记永久化实现区间取max,单点查
void update(int k,int l,int r,int x,int y,int v){
if(x<=l&&r<=y)tag[k]=max(tag[k],v);
else {
int mid=(l+r)>>1;
if(x<=mid)update(ls,l,mid,x,y,v);
if(mid<y)update(rs,mid+1,r,x,y,v);
}
}
void inquiry(int k,int l,int r,int pos){
res=max(res,tag[k]);
if(l^r){
int mid=(l+r)>>1;
if(pos<=mid)inquiry(ls,l,mid,pos);
else inquiry(rs,mid+1,r,pos);
}
}
}S,S_;
inline void extend(int a){
tmp=++tot;y=last;mxlen[tmp]=mxlen[y]+1;
for(;y&&!trans[y][a];y=link[y])trans[y][a]=tmp;
if(!y)link[tmp]=1;
else {
x=trans[y][a];
if(mxlen[x]==mxlen[y]+1)link[tmp]=x;
else {
xx=++tot;mxlen[xx]=mxlen[y]+1;link[xx]=link[x];
memcpy(trans[xx],trans[x],sizeof(trans[xx]));
for(;y&&trans[y][a]==x;y=link[y])trans[y][a]=xx;
link[tmp]=link[x]=xx;
}
}
last=tmp;
}
inline bool nroot(int x){return son[anc[x]][0]==x||son[anc[x]][1]==x;}
inline bool p(int x){return son[anc[x]][1]==x;}
inline void cov_(int x,int v){lst_[x]=lst[x];lst[x]=cov[x]=v;}
//将次右变为最右,最右由覆盖标记更新
inline void pushdown(int x){
if(cov[x]){
if(son[x][0])cov_(son[x][0],cov[x]);
if(son[x][1])cov_(son[x][1],cov[x]);
cov[x]=0;
}
}
void pushall(int x){if(nroot(x))pushall(anc[x]);pushdown(x);}
inline void rotate(int x){
rot_y=anc[x];rot_xx=anc[rot_y];rot_b=p(x);
if(nroot(rot_y))son[rot_xx][p(rot_y)]=x;
anc[x]=rot_xx;
anc[son[rot_y][rot_b]=son[x][!rot_b]]=rot_y;
anc[son[x][!rot_b]=rot_y]=x;
}
inline void splay(int x){
pushall(x);
for(;s_i=anc[x],nroot(x);rotate(x))if(nroot(s_i))rotate(p(x)==p(s_i)?s_i:x);
}
inline void access(int x,int pos){
for(y=0;x;y=x,x=anc[x]){
splay(x);son[x][1]=y;
l=lst[x]-mxlen[x]+1;r=lst[x];//l,r 为这个整个子串将要移动到的区间
if(lst[x]&&x^1)S.update(1,1,n,l,r,r),S_.update(1,1,n,1,l,r-l+1);
}//这里access由于每次是之前跳父亲来的,能保证x一定是当前splay中mxlen最大的
cov_(y,pos);
}
int to[N],nextn[N],h[N],id[N],edg;
inline void add(int x,int y,int i){to[++edg]=y,nextn[edg]=h[x],h[x]=edg;id[edg]=i;}
main(){
read(n);read(m);while(ch<97)ch=getchar();
register int i,j;
for(i=1;i<=n;++i)a=ch-97,extend(a),f[i]=tmp,ch=getchar();
for(i=1;i<=tot;++i)anc[i]=link[i];
for(i=1;i<=m;++i)read(x),read(y),add(y,x,i);
for(i=1;i<=n;++i){
access(f[i],i);
for(j=h[i];j;j=nextn[j]){
S.res=S_.res=0;
S.inquiry(1,1,n,to[j]);
S_.inquiry(1,1,n,to[j]);
res[id[j]]=max(S.res-to[j]+1,S_.res);
}
}
for(i=1;i<=m;++i)write(res[i]),putchar('\n');
}