1. 程式人生 > 實用技巧 >C++ 模板

C++ 模板

#樹鏈剖分

1,將樹從x到y結點最短路徑上所有節點的值都加上z

這也是個模板題了吧

我們很容易想到,樹上差分可以以O(n+m)的優秀複雜度解決這個問題

2,求樹從x到y結點最短路徑上所有節點的值之和

lca大水題,我們又很容易地想到,dfs O(n)預處理每個節點的dis(即到根節點的最短路徑長度)

dis(到根節點最短路徑長度)

lca性質:樹上兩點最短距離=dis(x)+dis(y)-2*dis(lca) O(mlogn+n)

樹鏈剖分 就是對一棵樹分成幾條鏈,把樹形變為線性,減少處理難度

需要處理的問題:

  • 將樹從x到y結點最短路徑上所有節點的值都加上z
  • 求樹從x到y結點最短路徑上所有節點的值之和
  • 將以x為根節點的子樹內所有節點值都加上z
  • 求以x為根節點的子樹內所有節點值之和

概念://可以先看程式碼再一一理解

  • 重兒子:對於每一個非葉子節點,它的所有兒子中 兒子數量最多的那一個兒子 為該節點的重兒子

  • 輕兒子:對於每一個非葉子節點,它的兒子中 非重兒子 的剩下所有兒子即為輕兒子

  • 葉子節點沒有重兒子也沒有輕兒子(因為它沒有兒子。。)非葉節點有且只有1個重兒子

  • 重邊:連線父親節點與重兒子的邊叫做重邊

  • 輕邊:剩下的即為輕邊

  • 重鏈:相鄰重邊連起來的的路徑叫重鏈

  • 輕鏈:輕邊連的路徑

  • 對於葉子節點,若其為輕兒子,則有一條以自己為起點的長度為1的鏈

    變數宣告:

    \(fa[i]~i的爸爸,d[i]~i的深度,siz[i]i為根的子樹節點個數,wson[i]儲存重兒子,rk[i]儲存當前dfs標號在樹中對應節點\)

    \(top[i]當前節點所在鏈頂端節點,id[i]~dfs執行順序(剖分後新編號)\)

    兩遍dfs

dfs1():

處理出siz和son陣列(如果一個點多個兒子子樹相等且最大,隨便找一個當重兒子)

順便記錄點的父親和深度,處理fa和d陣列,手動模擬一下吧

inline void dfs1(int x,int fa,int deep){
	dep[x] = deep;//標記每個點深度
	fa[x] = fa;
	siz[x] = 1;//標記每個非葉子節點的子樹大小,包含他自己
	//int maxson = -1;//記錄重兒子個數
	for(int i = head[x];i;i=edge[i].next){
	int y = edge[i].to;
	if(y == fa) continue;
	dfs1(y,x,deep+1);
	siz[x] += siz[y];	//son的siz已被處理,更新fa的siz
	if(siz[y] > siz[wson[x]]) wson[u]=y;//選最大siz
	}
}

dfs2():

連線重鏈,標記每個點的dfs序,為了用資料結構維護重鏈,dfs時保證一條重鏈各個節點dfs序連續

即處理出top,id,rk

void dfs2(int u,int t){//t重鏈頂端
	top[u] = t;//
	id[u]=cnt++; //標記dfs序
	rk[cnt] = u;//序號cnt對應節點v
	if(!wson[u]) return;
	dfs2(wson[u],t);
	//優先選擇進入重兒子來保證一條重鏈上各個節點dfs序連續
	//一個點和它的重兒子處於一條同一重鏈,所以重兒子頂端還是t
	for(int i=head[u];i;i=e[i].next){
		int v = e[i].to;
        if(v != wson[u] && v!=fa[u]) dfs2(v,v);//一個點位於鏈低端,那麼它的top必然是它本身
	}
}

為什麼是dfs2(v,v)呢,因為當v是重兒子的時候,它不可能為一條鏈的頂,因為根據重邊的定義,一定有一條邊連向重兒子,若重兒子為頂,還會有一條邊連向它,所以重兒子不會為頂端。

然後統計答案,中間一定需要別的資料結構

例題和程式碼就不放了,洛谷上有很多

pshttps://www.cnblogs.com/lykkk/p/10183778.html#autoid-3-0-0 這篇文章寫的也很好