點分治及題目

2022-02-27 09:02:31 字數 3105 閱讀 3613

一般可以用於處理大規模樹上路徑問題

既然是處理路徑問題,那麼可以把路徑分成兩種,經過當前根節點的路徑,不經過當前根節點的路徑

處理完經過當前根節點的路徑,然後刪掉根節點,此時肯定會形成乙個或多個子樹,那麼剩下的不經過當前根節點的路徑,遞迴到這些子樹中處理

刪掉的節點肯定在接下來的處理中就不會被考慮了,這就意味著並不會有路徑被重複統計

然而如果圖是乙個鏈,那麼一共要處理 \(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,對於第二個問題,對於每個重心算當前塊中每個點到重心的答案,然後由重心分開的塊要把多餘的資訊去...

點分治 理解及例題

點分治,基於點的分治,處理一些路徑計數問題 其思路為 子樹結構 子樹結構雖然的確是某點的乙個子樹,但我們討論點分治時,相當於把這個子樹摘下來,當做無根樹處理 對於乙個子樹結構 處理子樹結構中與某個單點 未必是原根 相關的路徑 以這個被處理的點為新根,找出她的子樹,變成遞迴下一層要處理的子樹結構 遞迴...