dsu on tree (樹上啟發式合併) 詳解
阿新 • • 發佈:2020-11-25
**一直都沒出過演算法詳解,昨天心血來潮想寫一篇,於是 dsu on tree 它來了**
## 1、前置技能
>1.鏈式前向星(vector 建圖)
>
>2.dfs 建樹
>
>3.剖分輕重鏈,輕重兒子
| 重兒子 | 一個結點的所有兒子中擁有最多子樹的兒子 |
| :----: | :------------------------------------: |
| 輕兒子 | 一個結點的所有兒子中不是重兒子的兒子 |
| 重邊 | 父親與重兒子的連邊 |
| 輕邊 | 父親與輕兒子的連邊 |
| 重鏈 | 一堆重邊連線而成的鏈 |
| 輕鏈 | 一堆輕邊連線而成的鏈 |
## 2、什麼是 dsu on tree(樹上啟發式合併) ?
>dsu on tree 其實就是個優雅的暴力演算法,和它一起共被稱為優雅暴力的演算法還有莫隊
>所謂優雅的暴力大概是指:“優雅思想,暴力的操作”
>例如莫隊我們知道它是將整個區間分塊,再將詢問的區間排序,最後暴力的維護所有詢問的區間
>其中 "整個區間分塊,詢問的區間排序" 為優雅的思想,而 "暴力的維護所有詢問的區間" 為暴力的操作
>因為需要將詢問的區間排序,我們就需要先將詢問的區間儲存下來,也就是要離線
>dsu on tree 和莫隊類似,也需要離線(它們同屬於靜態演算法)
## dsu on tree 優雅的思想:
>對於以 u 為根的子樹
>
>①. 先統計它輕子樹(輕兒子為根的子樹)的答案,統計完後刪除資訊
>
>②. 再統計它重子樹(重兒子為根的子樹)的答案 ,統計完後保留資訊
>
>③. 然後再將重子樹的資訊合併到 u上
>
>④. 再去遍歷 u 的輕子樹,然後把輕子樹的資訊合併到 u 上
>
>⑤. 判斷 u 的資訊是否需要傳遞給它的父節點(u 是否是它父節點的重兒子)
## dsu on tree 暴力的操作
> dsu on tree 暴力的操作體現於統計答案上(不同的題目統計方式不一樣)
## 3、dsu on tree 的過程演示及程式碼
### 1.圖示
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123222034632-1209682233.png)
>* 1 的重兒子為 2,輕兒子為 3
>
>* 2 的重兒子為 4,輕兒子為 5
>
>* 3 沒有重兒子,沒有輕兒子
>
>* 4 的重兒子為 6,沒有輕兒子
>
>* 5 的重兒子為 7,沒有輕兒子
>
>* 6 沒有重兒子,沒有輕兒子
>
>* 7 沒有重兒子,沒有輕兒子
>
為了更好觀看,我們將節點與其重兒子的連線描紅
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123222005211-1447137579.png)
>我們從根節點1進入,先找1的輕兒子,發現3,進入3
>
>3沒有別的兒子可以進入了,於是統計3的資訊
>
>統計完後即將返回父節點 1
>
>因為1-3的邊沒有被描紅邊、3不是1的重兒子(不傳遞3的資訊),所以刪除3的資訊再返回 1
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123221946203-1372610519.png)
>發現1沒有別的輕兒子了,就找重兒子,發現2,進入2
>
>進入2後,再找2的輕兒子,發現5,進入5
>
>發現5沒有輕兒子了,就找重兒子,發現7,進入 7
>
>7 沒有別的兒子可以進入了,於是統計 7 的資訊
>
>統計完後即將返回父節點 5
>
>因為邊5-7 有被描紅邊、7是5的重兒子,所以保留7的資訊直接返回 5(傳遞7的資訊的給5)
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123221912884-763716834.png)
> 5 所有兒子都進入過了,於是統計 5 的資訊
>
> 統計完後即將範圍父節點 2
>
> 因為邊2-5 沒有被描紅邊、5不是2的重兒子,所以刪除5的資訊再返回 2
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123221846572-1324981499.png)
> 發現2沒有其它輕兒子了,就找重兒子,發現4,進入4
>
> 發現4沒有其它輕兒子了,就找重兒子,發現6,進入6
>
> 6 沒有別的兒子可以進入了,於是統計 6 的資訊
>
> 統計完後即將返回父節點 4
>
> 因為邊4-6 有被描紅邊,6是4的重兒子,所以保留6的資訊直接返回 4(傳遞6的資訊的給4)
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123221806454-836162466.png)
>4 所有兒子都進入過了,於是統計 4 的資訊
>
>統計完後即將返回父節點 2
>
>因為邊2-4 有被描紅邊,4是2的重兒子,所以保留4的資訊直接返回2(傳遞4的資訊的給2)
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123221737255-1541446189.png)
>2 所有兒子都進入過了,於是統計 2 的資訊
>
>2 接受了4傳遞的資訊,但是並沒有接受5傳遞給它的資訊(被刪除了)
>
>於是 2 再進入5(輕兒子),統計一遍以 5 為根的子樹的資訊,再將該資訊合併到 2上
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123221621729-520988741.png)
>統計完後 2 後即將返回父節點 1
>
>因為邊1-2 有被描紅邊,2是1的重兒子,所以保留2的資訊直接返回1(傳遞2的資訊的給1)
>
>1 所有兒子都進入過了,於是統計 1 的資訊
>
>1 接受了2傳遞的資訊,但是並沒有接受3傳遞給它的資訊(被刪除了)
>
>於是 1 再進入3(輕兒子),統計一遍以 3 為根的子樹的資訊,再將該資訊合併到 1 上
>![](https://img2020.cnblogs.com/blog/1673058/202011/1673058-20201123221604511-1919733249.png)
>至此,整個 dsu on tree 的過程結束
### 2.程式碼
```cpp
struct Edge{
int nex , to;
}edge[N << 1];
int head[N] , TOT;
void add_edge(int u , int v) // 鏈式前向星建圖
{
edge[++ TOT].nex = head[u] ;
edge[TOT].to = v;
head[u] = TOT;
}
int sz[N]; // sz[u] 表示以 u 為根的子樹大小
int hson[N]; // hson[u] 表示 u 的重兒子
int HH; // HH 表示當前根節點的重兒子
void dfs(int u , int far)
{
sz[u] = 1;
for(int i = head[u] ; i ; i = edge[i].nex) // 鏈式前向星
{
int v = edge[i].to;
if(v == far) continue ;
dfs(v , u);
sz[u] += sz[v];
if(sz[v] > sz[hson[u]]) hson[u] = v; // 選擇 u 的重兒子
}
}
void calc(int u , int far , int val) // 統計答案
{
if(val == 1) ...; // val = 1,則新增資訊
else ...; // val = -1,則刪除資訊
......
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to;
if(v == far || v == HH) continue ; // 如果 v 是當前根節點的重兒子,則跳過
calc(v , u , val);
}
}
void dsu(int u , int far , int op) // op 等於0表示不保留資訊,等於1表示保留資訊
{
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to;
if(v == far || v == hson[u]) continue ; // 如果 v 是重兒子或者父親節點就跳過
dsu(v , u , 0); // 先遍歷輕兒子 ,op = 0 :輕兒子的答案不做保留
}
if(hson[u]) dsu(hson[u] , u , 1) , HH = hson[u];
// 輕兒子都遍歷完了,如果存在重兒子,遍歷重兒子(事實上除了葉子節點每個點都必然有重兒子)
// op = 1 , 保留重兒子的資訊
// 當前是以 u 為根節點的子樹,所以根節點的重兒子 HH = hson[u]
calc(u , far , 1); // 再次遍歷輕兒子統計答案
HH = 0; // 遍歷結束 ,即將返回父節點,所以取消標記 HH
if(!op) calc(u , far , -1); // 如果 op = -1,則 u 對於它的父親來說是輕兒子,不需要將資訊傳遞給它的父親
}
```
## 4.經典例題講解
| 題目編號 | 題目連結 | 題解連結 |
| :---------------- | ------------------------------------------------------- | ---------------------------------------------------- |
| CF600E | https://codeforces.com/problemset/problem/600/E | https://www.cnblogs.com/StarRoadTang/p/14028212.html |
| CF570D | https://codeforces.com/problemset/problem/570/D | https://www.cnblogs.com/StarRoadTang/p/14028239.html |
| CF208E | https://codeforces.com/problemset/problem/208/E | https://www.cnblogs.com/StarRoadTang/p/14028265.html |
| CF246E | https://codeforces.com/problemset/problem/246/E | https://www.cnblogs.com/StarRoadTang/p/14028271.html |
| CF1009F | https://codeforces.com/problemset/problem/1009/F | https://www.cnblogs.com/StarRoadTang/p/14028284.html |
| CF375D | https://codeforces.com/problemset/problem/375/D | https://www.cnblogs.com/StarRoadTang/p/14028290.html |
| wannafly Day2 E | https://ac.nowcoder.com/acm/contest/4010/E?&headNav=acm | https://www.cnblogs.com/StarRoadTang/p/14028296.html |
| ccpc2020長春站F題 | https://codeforces.com/gym/102832/problem/F | https://www.cnblogs.com/StarRoadTang/p/14028298.html |
| 洛谷P4149 | https://www.luogu.com.cn/problem/P4149 | https://www.cnblogs.com/StarRoadTang/p/14028300.html |
## 5.難題進階
這是道較難的題,聽說這也是 dsu on tree 的發明人專門為這個演算法出的題
| 題目編號 | 題目連結 | 題解連結 |
| -------- | -------------------------------------------- | ---------------------------------------------------- |
| CF741D | https://codeforces.com/contest/741/problem/D | https://www.cnblogs.com/StarRoadTang/p/14028301.html |
```markdown
┏┛ ┻━━━━━┛ ┻┓
┃ ┃
┃ ━ ┃
┃ ┳┛ ┗┳ ┃
┃ ┃
┃ ┻ ┃
┃ ┃
┗━┓ ┏━━━┛
┃ ┃ 神獸保佑
┃ ┃ 程式碼無BUG!
┃ ┗━━━━━━━━━┓
┃ ┣┓
┃ ┏┛
┗━┓ ┓ ┏━━━┳ ┓ ┏━┛
┃ ┫ ┫ ┃ ┫ ┫
┗━┻━┛ ┗━┻