Problem
Solution
月底写来凑数的文章
两个前缀的最长后缀长度就是在SAM上的len[lca]
,那么问题就变成了求编号在
[
L
,
R
]
[L,R]
[L,R] 内的两个节点的最深lca
我们可以考虑离线,枚举 r r r 并维护各个 l l l 的答案。从 r − 1 r-1 r−1 转移到 r r r,就是要加入 r r r 点的贡献。我们可以暴力跳 r r r 的祖先,在每个节点上记录其子树内已加入的最大的编号,这样就可以知道 l l l 最大为多少时可以使这个点做贡献,用线段树暴力取max即可。
有一个简单的优化,就是最大值相同的祖先可以跳过。这个操作其实就是相当于将点到根所有不同颜色的路径提取出来,并染成同一颜色,类似于lct中的access。而由于Tarjan大神证明了access的复杂度,所以时间复杂度大概是 O ( n log 2 n ) O(n\log^2 n) O(nlog2n)。
Code
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=200010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
struct query{
int l,r,id;
bool operator < (const query &b)const{return r<b.r;}
}q[maxn];
int n,m,pos[maxn],ans[maxn];
char s[maxn];
namespace SAM{
int sz,lst,ch[maxn][2],len[maxn],pre[maxn];
void insert(int c)
{
int p=lst,np=++sz;len[np]=len[p]+1;lst=np;
for(;p&&!ch[p][c];p=pre[p]) ch[p][c]=np;
if(!p) pre[np]=1;
else
{
int q=ch[p][c];
if(len[q]==len[p]+1) pre[np]=q;
else
{
int nq=++sz;len[nq]=len[p]+1;
memmove(ch[nq],ch[q],sizeof(ch[q]));
pre[nq]=pre[q];pre[np]=pre[q]=nq;
for(;ch[p][c]==q;p=pre[p]) ch[p][c]=nq;
}
}
}
}
using SAM::len;
namespace SGT{
int mx[maxn<<2];
void update(int l,int r,int pos,int val,int rt)
{
getmax(mx[rt],val);
if(l==r) return ;
int m=(l+r)>>1;
if(pos<=m) update(l,m,pos,val,rt<<1);
else update(m+1,r,pos,val,rt<<1|1);
}
int query(int l,int r,int L,int R,int rt)
{
if(L<=l&&r<=R) return mx[rt];
int m=(l+r)>>1,res=0;
if(L<=m) getmax(res,query(l,m,L,R,rt<<1));
if(m<R) getmax(res,query(m+1,r,L,R,rt<<1|1));
return res;
}
}
using SGT::update;
using SGT::query;
namespace LCT{
int top,stk[maxn],f[maxn],ch[maxn][2],cor[maxn];
int isroot(int x){return ch[f[x]][0]!=x&&ch[f[x]][1]!=x;}
void pushdown(int x){cor[ch[x][0]]=cor[ch[x][1]]=cor[x];}
void rotate(int x)
{
int fa=f[x],ff=f[fa],l,r;
l=(ch[fa][1]==x);r=l^1;
if(!isroot(fa)){if(ch[ff][0]==fa) ch[ff][0]=x;else ch[ff][1]=x;}
f[x]=ff;f[fa]=x;f[ch[x][r]]=fa;
ch[fa][l]=ch[x][r];ch[x][r]=fa;
}
void splay(int x)
{
stk[top=1]=x;
for(int i=x;!isroot(i);i=f[i]) stk[++top]=f[i];
for(int i=top;i;i--) pushdown(stk[i]);
while(!isroot(x))
{
int fa=f[x],ff=f[fa];
if(!isroot(fa))
{
if((ch[ff][0]==fa)^(ch[fa][0]==x)) rotate(x);
else rotate(fa);
}
rotate(x);
}
}
void access(int x,int c)
{
for(int t=0;x;t=x,x=f[x])
{
splay(x);
if(cor[x])
update(1,n,cor[x],len[x],1);
cor[x]=c;ch[x][1]=t;
}
}
}
using LCT::access;
void input()
{
read(n);read(m);scanf("%s",s+1);
SAM::sz=SAM::lst=1;
for(int i=1;i<=n;i++){SAM::insert(s[i]-'0');pos[i]=SAM::lst;}
for(int i=SAM::sz;i;i--) LCT::f[i]=SAM::pre[i];
for(int i=1;i<=m;i++){read(q[i].l);read(q[i].r);q[i].id=i;}
sort(q+1,q+m+1);
}
int main()
{
input();
for(int i=1,j=1;i<=n&&j<=m;i++)
{
access(pos[i],i);
for(;j<=m&&q[j].r<=i;j++) ans[q[j].id]=query(1,n,q[j].l,i,1);
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}