1. 程式人生 > 其它 >Hello World-JavaScript入門基礎(002)

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\)

\(ret\)

遍歷完後,該節點的答案就是\(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))\)

四、更多例題