洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree

原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html

题意

给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L...R] 的最长前后缀。

$$q,|S|\leq 2 \times 10 ^ 5$$

题解

真是一道有趣的字符串题。

首先我们给 S 建出 SAM ,并用线段树合并预处理出每一个节点的 Right 集合。

我们要做的是找到最大的 $p$ 满足 $p<R, S[L...p] = S[R-p+L...R]$ ,即 $p<R, p - LCS(S[1...p],S[1...R]) + 1 \leq L$ 。($p - LCS(S[1...p],S[1...R]) + 1$ 相当于算出 $LCS(S[1...p],S[1...R])$ 的左端点位置。)

由于两个前缀的 LCS 就是他们在 parent 树上的 LCA 的 Max,所以我们来用parent树上的节点来表示这个式子的意义:设 S[1...x] 在 parent 树上对应的节点为 pos(x) ,则我们就是要找到这样一个 $p$ ,它满足 $p<R,p-Max(LCA(pos(p),pos[R]))+1\leq L$ 。

上式等价于 $p<\min(R,L+Max(LCA(pos(p),pos(R)))$ 。有 min 很棘手,我们先把他干掉:考虑到 pos(R) 的祖先的 Max 是随着深度变小而递减的,那么如果我们从 pos(R) 开始,一步一步跳 father ,则一定会经过一个临界点 $x$ ,满足:在这个点以及它之前跳到的任意一点 $a$,满足 $R\leq L+Max(pos(a))$;在这个点之后跳到的任意一点 $b$ ,满足 $R\geq L+Max(pos(a))$ 。对于点 $x$ 以及之前的点,我们只需要在点 $x$ 的 Right 集合中找到 $<R$ 的最大的值就好了,这个东西线段树上二分就可以了。

现在考虑剩下一半,这一半只需要满足:

$$p-Max(LCA(pos(p),pos[R]))+1\leq L$$

我们来看看询问的时候需要干什么:询问先处理掉临界点以下的,直接从临界点开始跳。对于跳到的每一个节点,我们要干什么呢?设当前节点为 c ,设它为 LCA ,那么我们只要在它的 Right 集合中找到 $<L+Max(c)$ 的最大值就好了,同样还是线段树上二分。如果设 s 为 c 的一个儿子,它的子树中包含了 pos(R) ,你会发现我多算了这个子树的贡献。但是稍加思索就可以得知,它的贡献会在子树中再算一遍,而在子树中的 Max 比较大,所以再算一次不会更优。

我们考虑对 parent 树进行树链剖分。

现在我们已经给他树链剖分了,所以我们可以把临界点到根路径划分成 $O(\log n)$ 段重链,而且每一段都是某条重链的前缀。

考虑把询问进行离线,对于每一条重链按照深度顺序从浅到深加入当前节点的贡献(用线段树维护),即当前集合的 Right 集合内的所有元素 减去当前节点的 Max 然后+1,在加入的同时回答询问。这样做的复杂度显然是不对的!但是我们只要改一改就对了。对于一个节点 d 的重儿子的 Right 集合,d 为 LCA 时一定不比 重儿子为 LCA 时优,所以我们不加重儿子的 Right 集合的贡献,留到重儿子里面加。这样的话,每次询问的时候,询问的前缀重链的最深点要特殊处理。为什么这样时间复杂度是对的?这个东西其实就是 DSU on Tree 的复杂度,具体来说每一个点的对时间复杂度的贡献次数是它到根路径上轻重链切换的次数。

于是我们得到了一个 $O((|S|+q)\log |S|)$ 的做法。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
#define For(i,a,b) for (int i=a;i<=b;i++)
#define Fod(i,b,a) for (int i=b;i>=a;i--)
using namespace std;
typedef long long LL;
LL read(){
	LL x=0,f=0;
	char ch=getchar();
	while (!isdigit(ch))
		f|=ch=='-',ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
const int N=200005*2;
int n,Q;
int pnode[N];
char s[N];
vector <int> id;
namespace Mseg{
	const int S=N*25;
	int ls[S],rs[S],size;
	void Init(){
		size=0,clr(ls),clr(rs);
	}
	void Ins(int &rt,int L,int R,int x){
		if (!rt)
			rt=++size;
		if (L==R)
			return;
		int mid=(L+R)>>1;
		if (x<=mid)
			Ins(ls[rt],L,mid,x);
		else
			Ins(rs[rt],mid+1,R,x);
	}
	int Merge(int a,int b,int L,int R){
		if (!a||!b)
			return a+b;
		int rt=++size;
		if (L<R){
			int mid=(L+R)>>1;
			ls[rt]=Merge(ls[a],ls[b],L,mid);
			rs[rt]=Merge(rs[a],rs[b],mid+1,R);
		}
		return rt;
	}
	void GetR(int rt,int L,int R,vector <int> &v){
		if (!rt)
			return;
		if (L==R)
			return (void)v.push_back(L);
		int mid=(L+R)>>1;
		GetR(ls[rt],L,mid,v);
		GetR(rs[rt],mid+1,R,v);
	}
	int Getpre(int rt,int L,int R,int xR){// <xR
		if (!rt||L>=xR)
			return 0;
		if (L==R)
			return L;
		int mid=(L+R)>>1,v=Getpre(rs[rt],mid+1,R,xR);
		if (v)
			return v;
		else
			return Getpre(ls[rt],L,mid,xR);
	}
}
namespace SAM{
	struct Node{
		int Next[26],fa,Max,pos;
	}t[N];
	int last,root,size;
	int rt[N],id[N];
	void Init(){
		clr(t),Mseg::Init();
		last=root=size=1;
	}
	void extend(int c,int ps){
		int p=last,np=++size,q,nq;
		t[np].Max=t[p].Max+1,t[np].pos=ps,pnode[ps]=np;
		Mseg::Ins(rt[np],1,n,ps);
		for (;p&&!t[p].Next[c];p=t[p].fa)
			t[p].Next[c]=np;
		if (!p)
			t[np].fa=1;
		else {
			q=t[p].Next[c];
			if (t[p].Max+1==t[q].Max)
				t[np].fa=q;
			else {
				nq=++size;
				t[nq]=t[q],t[nq].Max=t[p].Max+1,t[nq].pos=ps;
				t[np].fa=t[q].fa=nq;
				for (;p&&t[p].Next[c]==q;p=t[p].fa)
					t[p].Next[c]=nq;
			}
		}
		last=np;
	}
	void Sort(){
		static int tax[N];
		clr(tax);
		For(i,1,size)
			tax[t[i].Max]++;
		For(i,1,size)
			tax[i]+=tax[i-1];
		For(i,1,size)
			id[tax[t[i].Max]--]=i;
	}
	void build(){
		Sort();
		Fod(i,size,2){
			int x=id[i],f=t[x].fa;
			rt[f]=Mseg::Merge(rt[f],rt[x],1,n);
		}
	}
}
using SAM::t;
vector <int> e[N],qs[N];
int fa[N][20],son[N],size[N],top[N],aI[N],Time;
void dfs(int x){
	fa[x][0]=t[x].fa,size[x]=1,son[x]=0;
	For(i,1,19)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for (auto y : e[x]){
		dfs(y);
		size[x]+=size[y];
		if (!son[x]||size[y]>size[son[x]])
			son[x]=y;
	}
}
void Get_Top(int x,int Top){
	top[x]=Top,aI[++Time]=x;
	if (son[x])
		Get_Top(son[x],Top);
	for (auto y : e[x])
		if (y!=son[x])
			Get_Top(y,y);
}
struct que{
	int L,R,ans;
	que(){}
	que(int _L,int _R){
		L=_L,R=_R;
	}
}q[N];
void ins_q(int L,int R,int id){
	int x=pnode[R];
	Fod(i,19,0)
		if (L+t[fa[x][i]].Max-1>=R)
			x=fa[x][i];
	q[id].ans=max(q[id].ans,Mseg::Getpre(SAM::rt[x],1,n,R));
	x=fa[x][0];
	while (x){
		q[id].ans=max(q[id].ans,Mseg::Getpre(SAM::rt[x],1,n,L+t[x].Max));
		assert(L+t[x].Max<=R);
		qs[x].push_back(id),x=fa[top[x]][0];
	}
}
namespace Seg{
	const int S=N*2*4;
	int ls[N],rs[N],Max[N],size;
	void Init(){
		while (size)
			ls[size]=rs[size]=Max[size]=0,size--;
	}
	void Ins(int &rt,int L,int R,int x,int v){
		if (!rt)
			rt=++size;
		Max[rt]=max(Max[rt],v);
		if (L==R)
			return;
		int mid=(L+R)>>1;
		if (x<=mid)
			Ins(ls[rt],L,mid,x,v);
		else
			Ins(rs[rt],mid+1,R,x,v);
	}
	int Query(int rt,int L,int R,int xL,int xR){
		if (!rt||L>xR||R<xL)
			return 0;
		if (xL<=L&&R<=xR)
			return Max[rt];
		int mid=(L+R)>>1;
		return max(Query(ls[rt],L,mid,xL,xR) ,Query(rs[rt],mid+1,R,xL,xR));
	}
}
void solve(){
	int sz=SAM::size,root;
	For(i,1,sz){
		if (i==1||!son[aI[i-1]])
			Seg::Init(),root=0;
		int x=aI[i];
		for (auto j : qs[x])
			q[j].ans=max(q[j].ans,Seg::Query(root,1,n,1,q[j].L));
		id.clear(),id.push_back(t[x].pos);
		for (auto y : e[x])
			if (y!=son[x])
				Mseg::GetR(SAM::rt[y],1,n,id);
		for (auto y : id)
			Seg::Ins(root,1,n,y-t[x].Max+1,y);
	}
}
int main(){
	scanf("%s",s+1),n=strlen(s+1);
	SAM::Init();
	For(i,1,n)
		SAM::extend(s[i]-'a',i);
	SAM::build();
	For(i,2,SAM::size)
		e[t[i].fa].push_back(i);
	dfs(1),Time=0,Get_Top(1,1),Q=read();
	For(i,1,Q){
		q[i].L=read(),q[i].R=read(),q[i].ans=0;
		ins_q(q[i].L,q[i].R,i);
	}
	solve();
	For(i,1,Q)
		printf("%d\n",max(0,q[i].ans-q[i].L+1));
	return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/zhouzhendong/p/LuoguP4482.html