點分治及其簡單運用

2022-05-19 06:14:41 字數 4153 閱讀 3386

《更新提示》

《第一次更新》

《正文》

點分治是一種用於處理靜態樹上路徑統計問題的演算法,其核心原理還是基於分治思想。

我們不妨對這類樹上靜態路徑統計問題抽象化,例如:

給定乙個棵無根樹,求滿足要求\(p\)的路徑有幾條。

我們可以使用分治演算法求解本題,對於無根樹,我們指定乙個節點為根,顯然,這樣的路徑可以歸為兩類:

\(1.\) 滿足要求\(p\)且經過根\(root\)的路徑

\(2.\) 滿足要求\(p\)但不經過根\(root\)的路徑

第二類路徑不經過根,所以一定在根的某一刻子樹中,這我們就可以遞迴地進行求解,而重點就在於統計第一類路徑。

先不考慮統計問題,我們可以設計如下的分治演算法:

\(1.\) 求出根\(root\)

\(2.\) 得到相關資訊,統計第一類路徑並累加入答案中

\(3.\) 刪除節點\(root\),對於\(root\)的每一棵子樹遞迴的執行分治演算法

顯然,遞迴的層數和每一次選取的根\(root\)有關,當樹退化成一條鏈時,如果任意的選取根,遞迴層數很可能就達到了\(o(n)\)級別。

解決方法就是選取樹的重心作為樹的根,容易證明,每一次選取樹的重心作為根時,點分治演算法最多遞迴\(log_2n\)層。

點分治的另乙個重要部分就是如何設計統計經過根的路徑的函式,通常來說,我們有兩種方法。

樹上直接統計

我們可以遍歷根的每一顆子樹\(s_i\),對於\(s_i\)中的每乙個節點,我們利用一層資料結構和之前子樹的節點進行統計答案,然後在再將這個子樹中的所有節點加入資料結構中。

單調性+尺取法

我們可以一遍\(dfs\)遍歷取出所有子節點,然後通過分析有關資訊的單調性,用尺取法在\(o(n)\)的時間內統計得到答案。

我們將結合兩道例題來分析這兩種統計方法。

給一顆 n 個節點的樹,每條邊上有乙個距離 v

定義 d(u,v) 為 u 到 v 的最小距離。

給定 k 值,求有多少點對 (u,v) 滿足:d(u,v)≤k。

輸入有多組資料, 以兩個 0 結尾。

每組資料第一行兩個整數 n,k。

接下來 n−1 行,每行三個整數 x,y,v,表示點 x 到點 y 有一條邊距離是 v。

對每組資料一行乙個答案。

5 4

1 2 3

1 3 1

1 4 2

3 5 1

0 0

8
一道裸的樹上路徑統計問題,我們使用點分治演算法。

其他部分只需要套用點分治演算法的模板就可以了,我們考慮如何設計統計函式。對於確定的根\(root\),我們可以通過一遍\(dfs\)求出每乙個點到根的距離。我們將每乙個點的距離取出來存在臨時陣列中並排序,那麼我們就可以使用尺取法統計答案了。顯然,對於乙個位置\(l\),滿足\(dis[l]+dis[r]\leq k\)的位置\(r\)是單調的,並且隨著\(l\)的增大,\(r\)的位置只會減小,那麼我們用雙指標就能統計答案。

還有乙個重複統計的問題,如果兩個點在同一棵子樹中,顯然這種不符合要求的方案也會被尺取法統計進去,我們需要用到容斥原理的思想,在遞迴求解根的每一顆子樹前,先再次的統計出這棵子樹中的答案,並在答案中減去這些方案即可。

\(code:\)

#include #include #include #include using namespace std;

const int n = 10020 , inf = 0x7f7f7f7f;

int n,k,head[n*2],t,tot,ans;

int dis[n],flag[n],temp[n];

int size[n],max[n],root;

struct edgee[n*2];

inline void insert(int x,int y,int v)

; head[x] = t;

}inline void input(void)

inline void dfs(int x,int f) }

inline int calc(int x,int len)

return res;

}inline void divide(int x)

}inline void reset(void)

int main(void)

return 0;

}

在兩周年紀念日的旅行之後,在第三年,旅行社spoj又一次踏上的打折旅行的道路。

這次旅行是icpc島嶼上進行的,乙個位於太平洋上,不可思議的小島。我們列出了n個地點(編號從1到n)供旅客遊覽。這n個點由n-1條邊連成乙個樹,每條邊都有乙個權值,這個權值可能為負。我們可以選擇兩個地點作為旅行的起點和終點。

由於當地正在慶祝節日,所以某些地方會特別的擁擠(我們稱這些地方為擁擠點)。旅行的組織者希望這次旅行最多訪問k個擁擠點。同時,我們希望我們經過的道路的權值和最大。

第一行,三個整數n,k,m(1 <= n <= 200000, 0 <= k <=m, 0 <= m <= n)

之後的m行,每行乙個擁擠點的編號。

最後的n-1行,每行三個整數u,v,l,代表u和v之間有一條權值為l的邊

乙個整數,權值和最大的旅行線路

8 2 335

71 3 1

2 3 10

3 4 -2

4 5 -1

5 7 6

5 6 5

4 8 3

12
題目大意:給定一棵樹,其中有若干個點是特殊點,求經過特殊點不超過\(k\)個的最長路徑長度。

我們還是考慮點分治演算法,顯然,我們可以把所有經過特殊點不超過\(k\)的路徑都統計出來,然後來更新最優解。

計算函式如何設計呢?我們就可以嘗試使用樹上直接統計的方法了。我們先一遍\(dfs\)求出每乙個節點到根的路徑上有幾個特殊點,然後建立乙個樹狀陣列,下標為經過特殊點的數量,值為路徑長度,並維護字首最大值。

對於根的每一顆子樹,我們掃瞄子樹中的每乙個節點,並在樹狀陣列中取出經過特殊點數量加上當前點經過特殊點數量不超過\(k\)的最大距離,在加上當前點到根的距離顯然就是乙個合法的備選答案,嘗試更新。

然後我們重新掃瞄子樹中的每乙個節點,把它加到樹狀陣列裡,就可以在下一棵子樹中更新了。

值得注意的是,在進行完當前的統計後,我們還需將樹狀陣列還原,需要在進行一次同樣的遍歷。

\(code:\)

#include using namespace std;

const int n = 200020 , inf = 0x7f7f7f7f;

struct binaryindexedtree

inline void reset(void)

inline void insert(int p,int v)

inline int query(int p)

inline void remove(int p)

}tree;

int n,k,m,head[n*2],t,tot,spe[n],ans,k_;

int dis[n],flag[n],cnt[n];

int size[n],max[n],root;

struct edgee[n*2];

inline void insert(int x,int y,int v)

, head[x] = t;

}inline void input(void)

for (int i=1;imax[x] ) root = x;

}inline void dfs(int x,int f)

}inline void calc(int x,int f)

}inline void updata(int x,int f,int type)

}inline void divide(int x)

tree.remove( cnt[x] + 1 );

for (int i=head[x];i;i=e[i].next)

for (int i=head[x];i;i=e[i].next)

}int main(void)

《後記》

求最近點對(分治演算法的運用)

原題 條件 多組測試資料,n個點 2 n 100000 接下來輸入n行點對,求點對之間距離的最小值的一半,保留兩位小數。解題思路 以x座標和y座標分別進行歸併排序,當x值相等時,以y值小的在前,當y值相等也是相似。解題 include include include define max 21474...

點分治 動態點分治

實在拖得太久了。先扔掉資料 分治的核心是盡量把乙個整體分成接近的兩個部分,這樣遞迴處理可以讓複雜度從n 變成nlogn。兩個問題,如何區分和如何算答案。對於第乙個問題,重心,然後就是找重心的方法,兩個dfs,對於第二個問題,對於每個重心算當前塊中每個點到重心的答案,然後由重心分開的塊要把多餘的資訊去...

點分治與動態點分治

點分治一般是用於解決樹上路徑問題。樹的重心 把重心這個點割掉後,使所形成的最大的聯通塊大小最小的點。可以證明重心子樹的大小最大不會超過 n over 2 重心可以通過 dfs 一遍求出。maxsiz x 表示割掉點x後所形成的的最大的聯通塊的大小 void dfs int x,int fa max ...