洛谷P4482 [BJWC2018]Border 的四種求法 字符串,SAM,線段樹合併,線段樹,樹鏈剖分,DSU on Tree

原文連接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.htmlhtml

題意

給定一個字符串 S,有 q 次詢問,每次給定兩個數 L,R ,求 S[L...R] 的最長先後綴。node

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

題解

真是一道有趣的字符串題。git

首先咱們給 S 建出 SAM ,並用線段樹合併預處理出每個節點的 Right 集合。ui

咱們要作的是找到最大的 $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])$ 的左端點位置。)spa

因爲兩個前綴的 LCS 就是他們在 parent 樹上的 LCA 的 Max,因此咱們來用parent樹上的節點來表示這個式子的意義:設 S[1...x] 在 parent 樹上對應的節點爲 pos(x) ,則咱們就是要找到這樣一個 $p$ ,它知足 $p<R,p-Max(LCA(pos(p),pos[R]))+1\leq L$ 。code

上式等價於 $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$ 的最大的值就行了,這個東西線段樹上二分就能夠了。htm

如今考慮剩下一半,這一半隻須要知足:blog

$$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^2 |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;
}
相關文章
相關標籤/搜索