Hello World-JavaScript入門基礎(002)
一、概述
\(dsu\ on\ tree\)通常用於解決子樹上的問題,要求無修改操作且允許離線。
對於這樣的問題,我們以前學過了像樹上莫隊、點分治等做法,但\(dsu\ on\ tree\)的複雜度遠優於他們
雖然\(dsu\ on\ tree\)複雜度優秀,但其實,它就是一個優雅的暴力:
遇到子樹問題,最暴力的做法毫無疑問就是暴力列舉子樹上的所有點統計答案,實際上\(dsu\ on\ tree\)就是這樣做的。
只不過,\(dsu\ on \ tree\)有著優雅的思想:輕重鏈剖分,它借用對每個點輕兒子與重兒子貢獻的分別處理,達到了\(\mathcal O(nlog(n))\)的複雜度。
二、實現
- 將詢問離線,記錄在子樹的根節點上
- 遍歷整棵樹,對於節點\(u\),先計算它輕兒子的答案,計算後刪除資訊
- 計算它重兒子的答案,不刪除資訊
- 將重子樹的資訊合併到\(u\)上
- 暴力遍歷\(u\)的輕子樹,將輕子樹的資訊合併到\(u\)上
- 處理\(u\)處的詢問
- 根據\(u\)是否是重兒子選擇是否刪除\(u\)的資訊
這就是\(dsu\ on\ tree\)的思想了,大家可能不太理解,我們從一道例題來感受一下:
三、例題
CF600E
題意:
-
給定一棵\(n\)個節點的以\(1\)為根的樹,每個節點都有一個顏色。
-
如果一種顏色在以\(x\)為根的子樹內出現次數最多,稱其在以\(x\)為根的子樹中佔主導地位
-
你的任務是對於每一個\(i \in[1,n]\),求出以\(i\) 為根的子樹中,占主導地位的顏色的編號和。
題解:
這是一道經典的\(dsu\ on\ tree\)題目了
考慮如何暴力做:顯然可以維護\(w[i]\)表示\(i\)這種顏色出現的次數,同時記錄\(ret\)表示目前處理的節點中出現次數最多的顏色的編號和。
當遍歷到\(u\)時,首先我們列舉它的輕兒子遞迴下去,遍歷輕兒子後,要刪除輕子樹節點對於\(w\)的影響
接著遍歷重子樹,這次我們保留這些節點的貢獻
那麼全域性變數中已經儲存了重子樹的資訊了,輕子樹的資訊我們直接暴力列舉,修改\(w\)
遍歷完後,該節點的答案就是\(u\)的答案,最後,刪除該節點的貢獻,也是暴力列舉它的子樹中的所有節點並刪去。
程式碼如下:
int hson[N],siz[N],w[N],mx,son;
ll ret,ans[N];
inline void dfs(int u,int f){
siz[u]=1;
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==f) continue;
dfs(v,u);
siz[u]+=siz[v];if(siz[v]>siz[hson[u]]) hson[u]=v;
}//輕重鏈剖分模板
}
inline void work(int u,int f,int tp){
w[col[u]]+=tp;//tp=1表示要增加這個節點的貢獻,-1則是減去該節點的貢獻
if(w[col[u]]>mx) mx=w[col[u]],ret=col[u];
else if(w[col[u]]==mx) ret+=col[u];//更新ret
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==f||v==son) continue;//son儲存的是重兒子,不要遍歷到重兒子去
work(v,u,tp);
}
}
inline void dsu(int u,int f,int tp){//tp=1表示不刪除資訊,tp=0表示要刪除
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==f||v==hson[u]) continue;
dsu(v,u,0); //處理輕兒子,要刪除資訊
}
if(hson[u]) dsu(hson[u],u,1),son=hson[u];//遍歷重兒子,不刪除資訊
work(u,f,1);//暴力遍歷輕子樹
son=0;//接下來刪除貢獻是暴力遍歷整個子樹而不僅是輕子樹了
ans[u]=ret;
if(!tp) work(u,f,-1),mx=0,ret=0;//直接刪除所有節點的貢獻
}
看起來十分暴力吧?它的複雜度其實確實是\(\mathcal O(nlog(n))\)的,這裡給出了粗略的證明:
首先,根據輕重鏈剖分的性質,每一個點到根的路徑上至多有\(\mathcal O(log(n))\)條輕邊。
考慮一個點\(u\)在什麼時候會被遍歷到:
\(u\)被一個祖先節點\(x\)統計當且僅當\(u\)在\(x\)的輕子樹上,也就是說\(x-u\)的這條鏈第一條邊是輕邊,那麼唯一一條輕邊對應唯一一個\(x\),所以至多被統計\(\mathcal O(log(n))\)次
\(u\)被一個祖先節點遍歷以刪除貢獻當且僅當\(x\)是一個輕兒子,在\(u\)的祖先中,輕兒子的數量不超過\(\mathcal O(log(n))\)個,於是也只會被遍歷\(\mathcal O(log(n))\)次
綜上,因為每個點的資訊修改是\(\mathcal O(1)\)的,所以總複雜度是\(\mathcal O(nlog(n))\)的
四、更多例題
- CF570D
- 咕咕咕