【算法雜談_02】樹鏈剖分

我們知道,對一段連續區間進行修改/求和/求極值……操作,可用線段樹等數據結構進行維護。那麼,如果這不是一個區間,而是一棵樹,我們又能怎樣解決?此時我們就用到了樹鏈剖分——這一專爲此類的問題而生的算法。

樹鏈剖分的原理是將一棵樹劃分爲幾部分並分別映射到一維區間中,這樣對樹進行操作就可以轉化爲對一維區間進行操作並用我們所熟悉的樹狀數組、線段樹、Splay……維護。

最常用的樹鏈剖分方法是輕重邊樹鏈剖分——將一棵樹中的邊分爲重兩類。首先對於每一個非葉子節點的節點,規定其重兒子爲它的子節點中以其爲根的子樹節點總數最大的一個,上述節點與其重兒子之間的連邊爲一條重邊,與其他子節點之間的連邊爲輕邊,於是整棵樹中每一條首尾相連的重邊都構成了一條重鏈,每一條輕邊都構成了一條輕鏈。

樹鏈剖分過程代碼實現:

首先對樹進行第一遍DFS,求出每一個節點的父節點、深度、以其爲根的子樹的節點總數和重兒子,再進行第二遍DFS,即可求出每一個節點所在重鏈的頂端和它在區間中對應的位置、區間中每一點對應的樹中的節點編號。

樹鏈剖分過程C++代碼實現:

void dfs(int x,int d)
{
	size[x]=1;deep[x]=d;
	for(int y,i=head[x];i;i=next[i])
		if((y=to[i])!=fa[x])
		{
			fa[y]=x;
			dfs(y,d+1);
			if(size[y]>size[son[x]])
				son[x]=y;
			size[x]+=size[y];
		}
}
void create(int x,int d)
{
	p_id[x]=++now;
	id_p[now]=x;
	top[x]=d;
	if(son[x])
		create(son[x],d);
	for(int y,i=head[x];i;i=next[i])
		if((y=to[i])!=fa[x]&&y!=son[x])
			create(y,y);
}

歷經兩遍DFS後整棵樹便剖分完成了,每一條重鏈上的點都依次被映射到連續區間上,若對一條重鏈進行區間操作,則可使用線性結構維護,將時間由O(N)降到了O(logN)!另外,樹鏈剖分的美妙性質:任意一點到根節點上的路徑上的鏈的個數不超過logN條,從而對任意兩點進行操作可通過枚舉路徑上重鏈進行操作而快速實現。下面列出求兩點間路徑上的點權和C++代碼:

int findsum(int x,int y)
{
	int f1=top[x],f2=top[y],re=0;
	while(f1!=f2)
	{
		if(deep[f1]<deep[f2])
			swap(x,y),swap(f1,f2);
		re+=query_sum(1,1,n,p_id[f1],p_id[x]);
		x=fa[f1];
		f1=top[x];
	}
	if(deep[x]>deep[y])
		swap(x,y);
	return re+query_sum(1,1,n,p_id[x],p_id[y]);
}

上面代碼中query_sum求同一條重鏈上兩點間路徑權值和(可用線段樹維護)。

至此,樹上區間操作問題被完美解決。

附模板題:【BZOJ1036】[ZJOI2008]樹的統計Count(具體代碼請參看本人其他博客)


【閱讀上一篇:【算法雜談_01】那些非主流排序算法

本文原創,轉載請註明出處

發佈了25 篇原創文章 · 獲贊 2 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章