一般可以用於處理大規模樹上路徑問題
既然是處理路徑問題,那麼可以把路徑分成兩種,經過當前根節點的路徑,不經過當前根節點的路徑
處理完經過當前根節點的路徑,然後刪掉根節點,此時肯定會形成乙個或多個子樹,那麼剩下的不經過當前根節點的路徑,遞迴到這些子樹中處理
刪掉的節點肯定在接下來的處理中就不會被考慮了,這就意味著並不會有路徑被重複統計
然而如果圖是乙個鏈,那麼一共要處理 \(n\) 個根節點,每次的處理一般不會低於 \(o(n)\),那麼複雜度大於 \(o(n^2)\) 不夠優秀
所以在選取根節點的時候,要選取當前子樹的重心,即當前子樹中,最大的子樹最小的乙個節點
重心的性質:以重心為根,重心的每個子樹的大小,都不會超過整個樹大小的一半,證明一下
因此,最多把整個樹遞迴 \(\log n\) 層即可,然後可以模擬在數列上的分治,同一遞迴深度處理的總複雜度和 \(n\) 有關,並且都相同,則複雜度是 \(o(\text\log n)\)
可以從這裡看樹的重心的其他性質的證明:
模板題:p3806 【模板】點分治1
給定一棵有 \(n\) 個點的樹,\(m\) 次詢問樹上距離為 \(k\) 的點對是否存在。
\(k\le 10^7,n\le 10^4,m\le 100\)
離線做,考慮能不能 \(o(n)\) 求出對於每乙個詢問,有沒有一條符合要求的穿過當前根節點的路徑
列舉當前根的每乙個子樹,算出子樹中每乙個點距離根的距離,存入 \(tmp\) 陣列,看一看之前是不是存在乙個長度為 \(k-tmp_i\) 的路徑
如何判斷是否存在?用乙個 \(judge\) 陣列,\(judge_i\) 表示目前有沒有一條從某個已經訪問過的子樹到根節點的,長度為 \(i\) 的路徑
就判斷 \(judge_\) 就行,當 \(tmp\) 所有元素判斷完之後,對於所有元素judege[tmp[i]]=1
這樣就能統計到所有的路徑,同時又不會出現路徑」折返「的情況,就是兩個在同一子樹中的節點被計算(因為他們的路徑不會經過當前的根)
然後每個 \(tmp\) 中的距離都要存一下,用來清空 \(judge\),不能memset
找重心的過程用find
函式實現,注意要呼叫兩遍,原因在注釋裡
複雜度 \(o(nm\log n)\)
#include#include#include#include#include#include#include#define reg register
#define en std::puts("")
#define ll long long
inline int read()
while(c>='0'&&c<='9')
return y?x:-x;
}#define n 10006
#define m 20006
struct graph
}g;int n,m,sum,root;//sum 記錄當前樹的大小,root 記錄當前找到的重心
int ask[106],ans[106];
int size[n],max[n],vis[n];//vis 記錄乙個節點是不是已經被刪除
int tmp[n],dis[n],que[n];
bool judge[10000006];
void find(int x,int fa)
for(reg int j=1;j<=tmp[0];j++)if(tmp[j]<=1e7)
} for(reg int i=1;i<=que[0];i++) judge[que[i]]=0;
}void divide(int u)
}int main()^n s(i, j)
\]求出所有的 \(sum_i\)
用點分治以後,問題變成如何求出以根為 lca 的點對的答案貢獻
如果有乙個點的顏色,在從根到它的路徑上是第一次出現,那麼他(設為 \(u\))可以為根的其它子樹的答案都貢獻上 \(size_u\)
因為其它的節點都可以有乙個到他子樹的路徑,會經過他,而他的顏色又是第一次出現
設 \(all=\sum size_u\)
再設 \(color\_sum_i\),為顏色 \(i\) 對 \(all\) 的貢獻的總值是多少,就是所有顏色在從根到它的路徑上是第一次出現,並且顏色是 \(i\) 的節點的 \(size\) 之和
我們可以通過一遍 dfs 來求出上面的資訊
然後列舉每乙個子樹,嘗試求出這個子樹中所有點的答案
首先肯定要把這個子樹對 \(all,color\_sum\) 的貢獻都去掉,因為上面說了這是對其它子樹答案的貢獻
去掉的過程稍有麻煩,具體看**能想明白
然後再來一遍 dfs,做出答案
我們設 \(num\) 為 \(u\) 到根路徑上的不同的顏色數,\(all'\) 是 \(all\)(減去此子樹貢獻的)減掉從 \(u\) 到路徑上每個不同顏色,的 \(color\_sum\) 和
則對答案的貢獻是 \(num\cdot (size_-size_x)-\sum color\_sum\),此處 \(x\) 是當前子樹的根
不難理解,\(\sum color\_sum\) 意思就是 \(u\) 想要到達根,就要經過這些顏色,所以再在其它子樹中經過,就不能再算了,貢獻不能計算進去
而一共有 \(size_-size_x\) 個點可以到達,沒到達乙個點,在從 \(u\) 到根的路徑中,都有 \(num\) 個不同顏色為答案產生貢獻,所以加上 \(num\cdot (size_-size_x)\)
最後做完這遍 dfs 要把剛才去掉的貢獻再加回去
然後列舉完清零,也是 dfs 實現
根節點的答案單獨計算,是 \(all-color\_sum(color_)+size_\),這點挺好理解
code
here
給一棵樹,每條邊有權。求一條簡單路徑,權值和等於 \(k\),且邊的數量最小。
裸的很,就維護距離的同時維護乙個 \(num_i\) 表示距離為 \(i\) 的點,最少經過幾條邊
然後把 \(tmp\) 往 \(judge\) 裡存的時候,再維護乙個 \(numed\),維護的是當前根的所有子樹的 \(num\),然後清空 \(num\) 陣列,更新答案
code
點分治 點分樹題目集
學了這麼久的點分治 點分樹,感覺自己還是只會做點裸題 這都要國賽了感覺自己吃棗藥丸。給定一棵 n 個點的樹,每條邊有乙個邊權。接下來有 m 次操作分為以下兩種 n,m le 3 times 10 5 tl 1.5s 原題範圍 n,m le 10 5,tl 4s 原題的做法是個不太優美的根號演算法,事...
點分治 動態點分治
實在拖得太久了。先扔掉資料 分治的核心是盡量把乙個整體分成接近的兩個部分,這樣遞迴處理可以讓複雜度從n 變成nlogn。兩個問題,如何區分和如何算答案。對於第乙個問題,重心,然後就是找重心的方法,兩個dfs,對於第二個問題,對於每個重心算當前塊中每個點到重心的答案,然後由重心分開的塊要把多餘的資訊去...
點分治 理解及例題
點分治,基於點的分治,處理一些路徑計數問題 其思路為 子樹結構 子樹結構雖然的確是某點的乙個子樹,但我們討論點分治時,相當於把這個子樹摘下來,當做無根樹處理 對於乙個子樹結構 處理子樹結構中與某個單點 未必是原根 相關的路徑 以這個被處理的點為新根,找出她的子樹,變成遞迴下一層要處理的子樹結構 遞迴...