點分治與動態點分治

2022-04-10 03:36:11 字數 4695 閱讀 2578

點分治一般是用於解決樹上路徑問題。

樹的重心:把重心這個點割掉後,使所形成的最大的聯通塊大小最小的點。

可以證明重心子樹的大小最大不會超過 \(n\over 2\)

重心可以通過 \(dfs\) 一遍求出。

//maxsiz[x] 表示割掉點x後所形成的的最大的聯通塊的大小

void dfs(int x,int fa)

max_siz[x] = max(max_siz[x],n-siz[x]);

if(max_siz[x] < max_siz[root]) root = x;

}

先來看到例題吧

給定一棵樹和乙個整數 \(k\),求樹上邊數等於 \(k\) 的路徑有多少條

我們的暴力做法就是列舉每條兩個點,然後在判斷他們兩個的距離是否為 \(k\)

大概是 o(\(n^3\)) 的複雜度,優化一下的話可以跑到 o(\(n^2 log n\))

這 \(n\) 的範圍一大,還是會 \(tle\), 考慮優化一下複雜度 。

我們現在主要想解決的是在以 \(s\) 為根的子樹中符合條件的路徑個數。

不難發現路徑一共可以分為三類:

情況一

從 \(s\) 出發到他的子樹中乙個點 \(t\) 所形成的路徑, 如圖中的黃色路徑:。

這個很好統計,可以直接由 \(s\)

\(dfs\) 一遍即可, 複雜度 \(o(m)\) 。( \(m\) 為路徑個數)

情況二:

不在 $s $ 的同乙個子樹中的兩個點 \(u,v\) 所形成的路徑,如圖。

顯然 \(u-v\) 的路徑是肯定要經過點 \(s\) 的,那麼 \(u-v\) 的路徑也就可以拆成 \(u-s\) 和 \(s-v\) 的兩條路徑。

這兩條路徑我們在解決情況 \(1\) 的時候已經求出來了,剩下的就是考慮怎麼把他們拼接起來。

設 \(d_u\) 表示 \(u\) 到 \(s\) 的距離,我們現在要解決的是 \(d_u + d_v = k\) 且 \(u\) 和 \(v\) 不在 \(s\) 的同一顆子樹中的情況。

我們可以把 \(d_u\) 按從小到大排一下序,根據單調性,利用雙指標就可以很好的解決這個問題。

但我們還要注意的是,需要排除兩條在同一顆子樹中的路徑的干擾。

具體的做法就是對每一條路徑記錄他位於 \(s\) 的那一顆子樹中。

這個可以和 \(d_u\) 一起在情況 \(1\) 的 \(dfs\) 中一併求出來。

假設路徑條數為 \(m\) ,那麼排序的複雜度為 \(o(mlogm)\), 雙指標的複雜度為 \(o(m)\), 所以總的複雜度為 \(o(mlogm)\)

那麼第二種情況我們就解決出來了。

情況三:

位於 \(s\) 的同一顆子樹中的 \(u,v\) 兩點形成的路徑,如圖:

這個顯然是當前問題的子問題,遞迴繼續求解即可。

三種情況我們已經考慮完了,現在分析一下時間複雜度的問題。

假設遞迴的深度為 \(k\), 每做一次的複雜度最壞為 \(o(nlogn)\) (主要來自於情況2的排序)。

那麼總的時間複雜度就是 \(o(knlogn)\), 所以為了保證複雜度,我們要使遞迴深度盡可能的小。

根據我們上面提到的關於重心的知識,每次分治的時候選取子樹的重心,這樣可以保證遞迴深度為 \(logn\)。

所以總的複雜度最壞為 \(o(nlog^2n)\) (實際上是很難達到這個上界的)

在做點分治的時候,我們需要把兩個子樹的資訊合併,我們暴力合併的複雜度過高,有的情況下會使用啟發式合併的方法來合併。

一般點分治的題套路都是一樣的,都是分治遞迴求解,唯一的不同點就在於怎麼合併子樹的資訊。

解決了合併子樹資訊這個問題,剩下的就是套模板的事情了。

例題**:

#include#include#includeusing namespace std;

const int n = 1e4+10;

int n,m,tot,cnt,sum_siz,root,u,v,w;

int head[n],siz[n],max_siz[n],dis[n],k[n];

bool vis[n],ans[n];

struct bian

e[n<<1];

struct node

; node(int x, int y)

}a[n];

inline int read()

while(ch >= '0' && ch <= '9')

return s * w;

}void add(int x,int y,int w)

bool comp(node a,node b)

void get_root(int x,int fa)//找重心

max_siz[x] = max(max_siz[x],sum_siz-siz[x]);

if(max_siz[x] < max_siz[root]) root = x;

}void get_dis(int x,int fa,int who)//找到重心的距離, who 記錄他是誰的子樹

}int search(int d)

else l = mid + 1;

}return res;

}void calc(int x,int d)

a[++cnt] = node(0,0);

sort(a + 1, a + cnt + 1, comp);//排一下序

for(int i = 1; i <= m; i++)

}}void slove(int x)

}int main()

for(int i = 1; i <= m; i++) k[i] = read();

max_siz[0] = sum_siz = n; root = 0;

get_root(1,0); slove(root);//找到一開始整顆樹的重心

for(int i = 1; i <= m; i++)

return 0;

}

題目描述

給定一棵 \(n\) 個節點的樹,每條邊有邊權,求出樹上兩點距離小於等於 \(k\) 的點對數量。

和模板題差不多,只要在合併子樹的時候稍微改一下即可。

code:

#pragma gcc optimize(2) 

#include#include#includeusing namespace std;

const int n = 4e4+10;

int n,m,tot,cnt,root,sum_siz,u,v,w,k,ans;

int head[n],siz[n],max_siz[n],dis[n],a[100010];

bool vis[n];

struct node

e[n<<1];

inline int read()

while(ch >= '0' && ch <= '9')

return s * w;

}void add(int x,int y,int w)

void get_root(int x,int fa)

max_siz[x] = max(max_siz[x],sum_siz-siz[x]);

if(max_siz[x] < max_siz[root]) root = x;

}void get_dis(int x,int fa)

}int calc(int x,int d)

sort(a+1,a+cnt+1);

int l = 1, r = cnt;

while(l <= r)

else r--;

}return res;

}void slove(int x)

}int main()

k = read();

max_siz[0] = sum_siz = n; root = 0;

get_root(1,0); slove(root);

printf("%d\n",ans);

return 0;

}

動態點分治又叫點分樹(個人覺得點分樹更形象一些),主要解決的樹上的一下帶修改問題。

動態點分治還是基於點分治的那套理論,每次選重心分治。

但考慮到修改操作,我們不可能每次修改都做一遍。

我們可以建點分樹來解決這個問題,具體來說就是:

設當前的分治中心為 \(x\), 由他子樹中的重心 \(y\) 向 \(x\) 連邊,不難發現這樣會構成一棵樹,這棵樹也被叫做點分樹。

不難發現當我們要修改 \(x\) 這個節點的資訊的時候,發現他會影響到的是 \(x\) 到根節點路徑上的點的資訊。

查詢的話同樣會用到 \(x\) 到根節點路徑上點的資訊。

由於我們樹的高度不超過 \(logn\), 所以直接暴力修改查詢即可。

我們就可以拿資料結構來維護每個點的資訊,巴拉巴拉。

**,咕咕咕

點分治 動態點分治

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

動態點分治

由於蒟蒻太遜,現在才開始學動態點分治,寫乙個 blog 吧。動態點分治是利用點分治的過程,建成一顆由子樹重心連線而成的點分樹,這棵樹的高度為 log n 級別的,因此可以通過暴力跳父親完成修改操作。建立點分樹的過程,就是按照點分治的流程,記錄上級重心並連線,即可獲得一棵點分樹。點分樹的結構與原樹不相...

動態點分治

首先你要先會點分治,然後動態點分治就是把點分治可持久化一下,讓其不用再每次詢問時都重新做一遍 具體就是,你考慮做點分的時候,你在每個分治重心上統計它所管轄的所有點的資訊,並計算答案。那麼每個點的資訊只會出現在它上面的分治重心中,所以我們把每層分治重心向上一層的連邊,這樣每個點的資訊只會出現都在它所有...