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 這篇文章寫的也很好