1. 程式人生 > 其它 >YbtOJ#532-往事之樹【廣義SAM,線段樹合併】

YbtOJ#532-往事之樹【廣義SAM,線段樹合併】

技術標籤:字串資料結構YbtOJ廣義SAM線段樹合併

正題

題目連結:https://www.ybtoj.com.cn/problem/532


題目大意

給出 n n n個點的一個 T r i e Trie Trie樹,定義 S x S_x Sx表示節點 x x x代表的字串
m a x { ∣ L C P ( S x , S y ) ∣ + ∣ L C S ( S x , S y ) ∣ } ( x ≠ y ) max\{|LCP(S_x,S_y)|+|LCS(S_x,S_y)|\}(x\neq y) max{LCP(Sx,Sy)+LCS(Sx,Sy)}(x=y)


L C P / L C S LCP/LCS LCP/LCS分別表示最長公共前/字尾)

1 ≤ n ≤ 2 × 1 0 5 1\leq n\leq 2\times 10^5 1n2×105


解題思路

正解好像是樹上 S A SA SA+線段樹合併的做法可是我不會,就寫了廣義 S A M SAM SAM

S A M SAM SAM p a r e n t s parents parents樹就是字尾樹,這裡給出了 T r i e Trie Trie樹就是一個構造廣義 S A M SAM SAM的好條件。
構造出來的廣義 S A M SAM SAM上的 p a r e n t s parents

parents樹上的 L C A LCA LCA l e n len len就是兩個串的 L C S LCS LCS
T r i e Trie Trie樹上的 L C A LCA LCA深度就是兩個串的 L C P LCP LCP

考慮列舉 T r i e Trie Trie樹上的一個點 x x x,求它的子樹中 L C S LCS LCS最大的一個點對,也就是字尾樹上 L C A LCA LCA最深。

對字尾樹求一個 d f s dfs dfs序,那麼最優的點對都只會出現在相鄰的點對中,這樣的點對數量不會很多,可以考慮一種列舉的方法。

可以使用線段樹合併,然後合併的時候每個節點維護一個改區間內最左/右的值。然後拿左區間的最右值和右區間的最左值計算答案就好了。

時間複雜度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n),如果肯寫 S T ST ST表求 L C A LCA LCA可以做到 O ( n log ⁡ n ) O(n\log n) O(nlogn)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int N=4e5+10,T=20,M=N<<5;
struct node{
	int to,next,w;
}a[N];
int n,tot,cnt,ans,ls[N],len[N],fa[N],f[N][T+1];
int rfn[N],dfn[N],g[N],rt[N],dep[N],p[N];
vector<int>G[N];map<int,int>ch[N];
void addl(int x,int y,int w){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;a[tot].w=w;
	return;
}
int Insert(int p,int c){
	int np=++cnt;len[np]=len[p]+1;
	for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
	if(!p)fa[np]=1;
	else{
		int q=ch[p][c];
		if(len[p]+1==len[q])fa[np]=q;
		else{
			int nq=++cnt;ch[nq]=ch[q];
			len[nq]=len[p]+1;fa[nq]=fa[q];
			fa[np]=fa[q]=nq;
			for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
		}
	}
	return np;
}
void dfs(int x){
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		p[y]=Insert(p[x],a[i].w);
		dfs(y);
	}
	return;
}
void dfs2(int x){
	dfn[++cnt]=x;rfn[x]=cnt;
	for(int i=0;i<G[x].size();i++){
		dep[G[x][i]]=dep[x]+1;
		dfs2(G[x][i]);
	}
	return;
}
int LCA(int x,int y){
	x=dfn[x];y=dfn[y];
	if(dep[x]>dep[y])swap(x,y);
	for(int i=T;i>=0;i--)
		if(dep[f[y][i]]>=dep[x])
			y=f[y][i];
	if(x==y)return x;
	for(int i=T;i>=0;i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
struct SegTree{
	int cnt,ls[M],rs[M],l[M],r[M];
	void Change(int &x,int L,int R,int pos){
		if(!x)x=++cnt;l[x]=r[x]=pos;
		if(L==R)return;
		int mid=(L+R)>>1;
		if(pos<=mid)Change(ls[x],L,mid,pos);
		else Change(rs[x],mid+1,R,pos);
		return;
	}
	int Merge(int &x,int y,int L,int R){
		if(!x||!y){x=x|y;return 0;}
		int mid=(L+R)>>1,ans=0;
		ans=Merge(ls[x],ls[y],L,mid);
		ans=max(ans,Merge(rs[x],rs[y],mid+1,R));
		l[x]=ls[x]?l[ls[x]]:l[rs[x]];
		r[x]=rs[x]?r[rs[x]]:r[ls[x]];
		if(ls[x]&&rs[x])ans=max(ans,len[LCA(r[ls[x]],l[rs[x]])]);
		return ans;
	}
}Tr;
void dfs3(int x,int dep){
	Tr.Change(rt[x],1,cnt,rfn[p[x]]);
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		dfs3(y,dep+1);
		g[x]=max(g[x],g[y]);
		g[x]=max(g[x],Tr.Merge(rt[x],rt[y],1,cnt));
	}
	ans=max(ans,g[x]+dep);
	return;
}
int main()
{
	freopen("recollection.in","r",stdin);
	freopen("recollection.out","w",stdout);
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		int x,w;
		scanf("%d%d",&x,&w);
		addl(x,i,w);
	} 
	cnt=p[1]=1;dfs(1);
	for(int i=2;i<=cnt;i++)
		G[fa[i]].push_back(i),f[i][0]=fa[i];
	cnt=0;dfs2(1);
	for(int j=1;j<=T;j++)
		for(int i=1;i<=cnt;i++)
			f[i][j]=f[f[i][j-1]][j-1];
	dfs3(1,0);
	printf("%d\n",ans);
}