1. 程式人生 > >dsu on tree (樹上啟發式合併) 詳解

dsu on tree (樹上啟發式合併) 詳解

**一直都沒出過演算法詳解,昨天心血來潮想寫一篇,於是 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! ┃   ┗━━━━━━━━━┓ ┃        ┣┓ ┃     ┏┛ ┗━┓ ┓ ┏━━━┳ ┓ ┏━┛ ┃ ┫ ┫ ┃ ┫ ┫ ┗━┻━┛ ┗━┻