一直都沒出過算法詳解,昨天心血來潮想寫一篇,因而 dsu on tree 它來了html
1.鏈式前向星(vector 建圖)算法
2.dfs 建樹markdown
3.剖分輕重鏈,輕重兒子3d
重兒子 | 一個結點的全部兒子中擁有最多子樹的兒子 |
---|---|
輕兒子 | 一個結點的全部兒子中不是重兒子的兒子 |
重邊 | 父親與重兒子的連邊 |
輕邊 | 父親與輕兒子的連邊 |
重鏈 | 一堆重邊鏈接而成的鏈 |
輕鏈 | 一堆輕邊鏈接而成的鏈 |
dsu on tree 其實就是個優雅的暴力算法,和它一塊兒共被稱爲優雅暴力的算法還有莫隊
所謂優雅的暴力大概是指:「優雅思想,暴力的操做」
例如莫隊咱們知道它是將整個區間分塊,再將詢問的區間排序,最後暴力的維護全部詢問的區間
其中 "整個區間分塊,詢問的區間排序" 爲優雅的思想,而 "暴力的維護全部詢問的區間" 爲暴力的操做
由於須要將詢問的區間排序,咱們就須要先將詢問的區間保存下來,也就是要離線
dsu on tree 和莫隊相似,也須要離線(它們同屬於靜態算法)code
對於以 u 爲根的子樹htm
①. 先統計它輕子樹(輕兒子爲根的子樹)的答案,統計完後刪除信息blog
②. 再統計它重子樹(重兒子爲根的子樹)的答案 ,統計完後保留信息排序
③. 而後再將重子樹的信息合併到 u上get
④. 再去遍歷 u 的輕子樹,而後把輕子樹的信息合併到 u 上table
⑤. 判斷 u 的信息是否須要傳遞給它的父節點(u 是不是它父節點的重兒子)
dsu on tree 暴力的操做體現於統計答案上(不一樣的題目統計方式不同)
1 的重兒子爲 2,輕兒子爲 3
2 的重兒子爲 4,輕兒子爲 5
3 沒有重兒子,沒有輕兒子
4 的重兒子爲 6,沒有輕兒子
5 的重兒子爲 7,沒有輕兒子
6 沒有重兒子,沒有輕兒子
7 沒有重兒子,沒有輕兒子
爲了更好觀看,咱們將節點與其重兒子的連線描紅
咱們從根節點1進入,先找1的輕兒子,發現3,進入3
3沒有別的兒子能夠進入了,因而統計3的信息
統計完後即將返回父節點 1
由於1-3的邊沒有被描紅邊、3不是1的重兒子(不傳遞3的信息),因此刪除3的信息再返回 1
發現1沒有別的輕兒子了,就找重兒子,發現2,進入2
進入2後,再找2的輕兒子,發現5,進入5
發現5沒有輕兒子了,就找重兒子,發現7,進入 7
7 沒有別的兒子能夠進入了,因而統計 7 的信息
統計完後即將返回父節點 5
由於邊5-7 有被描紅邊、7是5的重兒子,因此保留7的信息直接返回 5(傳遞7的信息的給5)
5 全部兒子都進入過了,因而統計 5 的信息
統計完後即將範圍父節點 2
由於邊2-5 沒有被描紅邊、5不是2的重兒子,因此刪除5的信息再返回 2
發現2沒有其它輕兒子了,就找重兒子,發現4,進入4
發現4沒有其它輕兒子了,就找重兒子,發現6,進入6
6 沒有別的兒子能夠進入了,因而統計 6 的信息
統計完後即將返回父節點 4
由於邊4-6 有被描紅邊,6是4的重兒子,因此保留6的信息直接返回 4(傳遞6的信息的給4)
4 全部兒子都進入過了,因而統計 4 的信息
統計完後即將返回父節點 2
由於邊2-4 有被描紅邊,4是2的重兒子,因此保留4的信息直接返回2(傳遞4的信息的給2)
2 全部兒子都進入過了,因而統計 2 的信息
2 接受了4傳遞的信息,可是並無接受5傳遞給它的信息(被刪除了)
因而 2 再進入5(輕兒子),統計一遍以 5 爲根的子樹的信息,再將該信息合併到 2上
統計完後 2 後即將返回父節點 1
由於邊1-2 有被描紅邊,2是1的重兒子,因此保留2的信息直接返回1(傳遞2的信息的給1)
1 全部兒子都進入過了,因而統計 1 的信息
1 接受了2傳遞的信息,可是並無接受3傳遞給它的信息(被刪除了)
因而 1 再進入3(輕兒子),統計一遍以 3 爲根的子樹的信息,再將該信息合併到 1 上
至此,整個 dsu on tree 的過程結束
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 對於它的父親來講是輕兒子,不須要將信息傳遞給它的父親 }
這是道較難的題,據說這也是 dsu on tree 的發明人專門爲這個算法出的題
題目編號 | 題目連接 | 題解連接 |
---|---|---|
CF741D | https://codeforces.com/contest/741/problem/D | http://www.javashuo.com/article/p-pthqnupk-nx.html |
┏┛ ┻━━━━━┛ ┻┓ ┃ ┃ ┃ ━ ┃ ┃ ┳┛ ┗┳ ┃ ┃ ┃ ┃ ┻ ┃ ┃ ┃ ┗━┓ ┏━━━┛ ┃ ┃ 神獸保佑 ┃ ┃ 代碼無BUG! ┃ ┗━━━━━━━━━┓ ┃ ┣┓ ┃ ┏┛ ┗━┓ ┓ ┏━━━┳ ┓ ┏━┛ ┃ ┫ ┫ ┃ ┫ ┫ ┗━┻━┛ ┗━┻━┛