點分治學習筆記

2022-05-16 11:18:57 字數 2721 閱讀 8082

銀月城傳送門

集訓day9上午各種神仙分治真心聽不懂,自己頹著把點分治學了,簡單(並不)寫一下再加深一下理解吧。

一、點分治是個啥

點分治是通常用來處理樹上路徑統計問題的一種分治方法。尤其適用於大規模的資料處理。時間複雜度最高為o(nlogn)。

例如求樹上兩點間距離,點分治就遠優於dfs求兩點距離(o(n^3)複雜度)

二、點分治的實現原理

以求樹上兩點路徑為例子,讓我們構想一棵鼠:

姑且將其命名為小撈鼠。

考慮路徑問題,假設一條滿足條件的路徑經過點 1 ,那麼會出現兩種情況:

1.這條路徑在 x 的乙個子鼠裡(以 1 為端點)

2.在 1 的兩個不同的子鼠裡。

這時候乙個想法就會是從根節點出發進行dfs,分別處理紫薯子樹紫鼠子鼠的答案。

等等,都說了路徑經過點 1 可能產生兩種情況,莫非是要分類討論?小撈鼠這麼友善肯定不會讓我們分類討論的。

並不會,對於情況 2,我們可以果斷直接分治,假設要求出點 4 到點 7 的路徑,我們可以分別處理 4 和 7 ,用dis表示當前結點到根節點 1 的路徑長度, 則 4 到 7 的路徑長即為dis[4]+dis[7]。

再看情況1,假設我們要求點 4 到點 5 的路徑,則既然它們之間的路徑包含在 1 的某個子樹內, 那麼我們就可以找到這棵子鼠的根,然後按第一類情況處理。

於是分治的思想就形成了。

三、點分治的具體實現

上文提到,我們的想法是「從根節點出發進行dfs,分別處理紫薯子樹紫鼠子鼠的答案。

」那麼問題又來了,我們該從哪個根節點出發呢?

考慮一下模擬,若是在鏈上進行分治,想必是從中間分為最佳,為什麼呢?因為這樣分可以使各部分盡量平均,遞迴效率就會最大化。

那麼我們只要找到乙個節點,使該節點被刪除後形成的各子鼠肥大啊不大小盡量平均。因為這樣才能最大程度減小這棵鼠在扣籃時因為重心不穩而滑倒的可能!

重心概念應運而生。

定義:找到乙個點,其所有的子樹中最大的子鼠節點數最少,那麼這個點就是這棵鼠的重心,刪去重心後,生成的多棵鼠盡可能平衡

以下為求重心**,幫助理解:(sum代表以父節點為根的子鼠大小)

void getroot(int id,int

fa) maxp[id]=max(maxp[id],sum-size[id]);//

與父節點的另一子鼠比較

if(maxp[id]//

若該子鼠小,更新重心

root=id;

}

於是來到了最重要的分治部分,這個部分有點難懂我要結合一下真正的題幹:

給定一棵有n個點的樹

詢問樹上距離為k的點對是否存在。

(多次詢問&&可離線)

求的不是某條路徑,而是從所有可能的路徑中統計答案。

這好辦啊,先離線所有的詢問,將k儲存在乙個query陣列裡,列舉所有可能路徑,每次掃一遍query陣列,若路徑長度=k就標記該次詢問,最後輸出標記就可以了。

那怎麼用分治實現呢?

從重心向下列舉子鼠根節點,根節點又會形成子鼠為v1,v2……vt, 我們從第乙個子鼠v1開始,求出每棵子鼠的dis

假設當前處理的子鼠為vi, 對離線記錄的每個詢問, 列舉v1到vi-1的dis與當前子鼠的dis的和, 若相等就標記

路徑加和後將這棵子鼠的dis與前面的dis一起儲存於乙個陣列, 當這個根節點的子鼠查詢完後清空這個陣列

然後對其他子鼠進行分治,可以保證所有路徑都被列舉過。

end有講的不清楚的地方隨時私信本蒟蒻,下方的**給出了詳細注釋

die碼如下:

#include

#include

#include

#include

#include

#define inf  10000000

#define maxn 100010

using namespace std;

int n,m;

int cnt,nxt[maxn<<1],to[maxn<<1],head[maxn],val[maxn<<1];//鏈前存邊用的

int maxp[maxn],size[maxn],dis[maxn],rem[maxn],tot;//maxp求重心時儲存最大子鼠

int vis[maxn],test[inf],ju[inf],q[maxn];//vis為標記陣列,test儲存每個詢問的結果,ju[i]表示到根距離為i的路徑是否存在

int query[1010];//儲存詢問

int sum,root;

void addedge(int x,int y,int v)

void getroot(int id,int fa)//求重心,詳細注釋見上文

maxp[id]=max(maxp[id],sum-size[id]);

if(maxp[id]=rem[j])

test[k]|=ju[query[k]-rem[j]];//與詢問比較,記錄答案

for(int j=rem[0];j;j--)

}for(int i=1;i<=now;i++)

ju[q[i]]=0;//為下一棵子鼠清空

}void solve(int id)

}int main()

{scanf("%d%d",&n,&m);

for(int i=1;i

點分治學習筆記

點分治主要用來處理樹上路徑問題,可以統計樹上點到點的所有路徑,複雜度o nlogn 基於樹上的結點進行分治,不斷將一棵樹拆成多顆子樹處理 選擇點時為了防止退化成鏈的情況,如果選點後左右子樹越大,遞迴層數越多,時間越慢,反之則越快,我們每次選擇子樹內的重心 void getroot int u,int...

點分治學習筆記

關於點分治,其實思想是非常好理解的,模擬在數列上或是在平面上的分治演算法 如歸併排序,平面最近點對等 我們可以從字面上理解該演算法 以乙個點為界限,將一棵樹分成若干個子樹,當劃分到一定規模,就對每個子樹分別進行求解 感性理解就好了 感受乙個演算法最直觀的辦法,就是來看一道模板題。給定一棵有 n 個點...

點分治學習筆記

point divide and rule 澱粉質就是在樹上,依靠不停的遞迴和分治,解決相同的子問題 先來看看模板題 tree 就是找樹上 k 的路徑有多少 我們可以分兩種情況討論 1.經過根節點 p 的路徑 2.不經過根節點 p 的路徑 第二種情況可以通過遞迴來處理,我們直接來討論第一種情況 設當...