點分治初探

2022-04-29 20:15:10 字數 4172 閱讀 6723

點分治,適用於樹上的路徑統計問題,本質上是用分治思想優化的暴力

其實學會思想就很簡單了。

給定一棵樹和乙個整數 \(k\),求樹上長度為 \(k\) 的路徑總數。

顯然共有 \(n(n-1)\) 條路徑,暴力統計複雜度過高。

考慮路徑情況,發現我們可以將其分為兩類:

經過根節點的路徑。

不經過根節點的路徑。

運用分治思想可以發現,我們可以只處理情況一,情況二直接分治下去即可。

至於根節點的選定之後再談。

針對情況一,我們假設根節點為 \(p\),路徑兩端點為 \(x,y\),顯然 \(x,y\) 屬於 \(p\) 的不同子樹。

利用這個思想可以進一步簡化操作,只需要針對以 \(p\) 為根節點的樹進行統計即可。

顯然時間複雜度與分治的次數相關,我們希望分治的次數盡量少。

這就要求以選定節點為根的子樹層數盡量少,顯然選取重心是最優的。

那麼可以證明最大分治次數為 \(\log n\) 次。

在執行 \(calc(p)\) 操作是我們通常有兩種方法,各有優劣。

\(o(n\log n)\):對空間要求較大,主要思想是用桶/樹狀陣列來統計之前子樹的答案,然後統計當前答案並不斷重新整理。(值得注意的是樹狀陣列的常數與路徑長度有關,可能常數較大)

\(o(n \log^2 n)\):時間換空間,主要思想是將路徑長度排序後利用單調性使用雙指標等方法統計答案,顯然空間與路徑長度無關,更具有普適性。

還有就是最好不要在 \(calc(p)\) 中使用memset來盲目初始化,這樣會使時間複雜度大量增加。

這裡以模板題為例來講解。

給定一棵有 \(n\) 個點的樹,詢問樹上距離為 \(k\) 的點對是否存在。

不管怎麼樣,點分治的題目都是先找重心,顯然樹的重心可以用 dfs 簡單解決。

void get_root(int u,int fa,int sum)

mxp[u]=max(mxp[u],sum-sz[u]);

if(!root || mxp[root]>mxp[u]) root=u;//更新

return;

}//下面是呼叫

root=0;

get_root(u,0,sz[u]);

root_u=root;

然後是千篇一律的分治**,同樣用 dfs 解決,最大深度為log n層。

void dfs(int u)

return;

}

顯然核心**就是 \(calc(u)\) 了,本題對空間要求不高,可以使用桶/雙指標來通過,這裡採用**更長的雙指標。

int dis[n],a[n],b[n],tot=0;

void get_dis(int u,int fa,int d,int anc)

return;

}bool cmp(int x,int y)

else

} }return;

}

綜合起來就可以很好的通過本題,**不再重複給出。

tree

統計一棵有 \(n\) 個節點的樹中有多少條路徑邊權和 \(\leq k\)。

和模板題比較類似,只需要將桶改為樹狀陣列統計即可,時間複雜度 \(o(n\log m)\)。

但是由於樹狀陣列總值 \(m\leq 4\times 10^7\),時間複雜度遠大於雙指標的 \(o(n\log n)\),所以這裡介紹第二種方法。

總體沒有大變化,只是 \(calc(p)\) 中有所改動。

考慮把樹中的每乙個點放入 \(a\) 陣列中,同時預處理出 \(dis[i]\) 和 \(b[i]\),分別為節點 \(i\) 到根的距離,和所屬的子樹。

將 \(a\) 陣列按照 \(dis\) 從小到大排序,用 \(l,r\) 兩個指標分別從前、後掃瞄 \(a\) 陣列。

根據單調性,只需要不斷移動指標即可。

但是我們還需要排除兩點在同一子樹的情況,用 \(p[s]\) 維護在 \(l+1\) 到 \(r\) 之間滿足 \(b[a[i]]=s\) 的節點數。

顯然 \(r-l-p[b[a[i]]]\) 即為所求值。

**如下:

#include#include#include#include#include#define n 40010

using namespace std;

int n,k,cnt,ans;

int root=0,mxp[n],sz[n];

int head[n];

struct edgeed[n<<1];

bool vis[n];

int read()

void add(int u,int v,int w)

void get_root(int u,int fa,int sum)

mxp[u]=max(mxp[u],sum-sz[u]);

if(!root || mxp[u][國家集訓隊]聰聰可可

統計樹中有多少邊權和為 \(3\) 的倍數的路徑。

用桶分別維護不同餘數的路徑個數即可。

#include#include#include#include#include#define n 20010

using namespace std;

int n,cnt,root,ans=0;

int head[n];

int mxp[n],sz[n];

struct edgeed[n<<1];

bool vis[n];

int read()

void add(int u,int v,int w);

head[u]=cnt;

return;

}void get_root(int u,int fa,int sum)

mxp[u]=max(mxp[u],sum-sz[u]);

if(!root || mxp[u]求一條簡單路徑,權值和等於 \(k\),且邊的數量最小。

顯然沒有數量最小的條件就是模板題了,加了的話...用桶維護即可。

值得注意的是本題用 \(o(n\log^2 n)\) 的雙指標做法只能得到90 pts,必須使用 \(o(n\log n)\) 的桶。

#include#include#include#include#include#define n 2000010

#define inf 0x3f3f3f3f

using namespace std;

int n,m,k,ans=inf;

int root,cnt=0;

int head[n];

struct nodeed[n<<1];

bool vis[n];

int read()

void add(int u,int v,int w)

int sz[n],mxp[n];

void get_root(int u,int fa,int sum)

mxp[u]=max(mxp[u],sum-sz[u]);

if(!root || mxp[root]>mxp[u]) root=u;

return;

}int dis[n],d[n],minn[n],tot=0;

void get_dis(int u,int fa,int dist,int dist2)

return;

}void calc(int u)

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

minn[dis[i]]=inf;

return;

}void dfs(int u)

return;

}int main(){

n=read(),k=read();

for(int i=1;i點分治可以很好的解決樹上路徑統計問題,使用面較廣,而且據說是近期省選熱門考點。

仍需繼續學習點分樹。

完結撒花。

初探點分治

給定一棵數,問樹中長度 k的簡單路徑有多少條。如果純dfs的話,時間複雜度是o n 3 o n 3 o n3 必然超時。於是我們考慮用點分治來做。主要演算法流程有以下幾步 求樹的重心 乙個點,其所有的子樹中最大的子樹節點數最少 p 從p出發進行dfs,將子樹節點存起來並按照權值排序 進行calc 操...

點分治 動態點分治

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

點分治與動態點分治

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