學習筆記 點分治

2022-04-07 10:02:03 字數 1725 閱讀 7655

點分治, 其實應該叫 "樹上點分治".

主要用於解決 "樹上路徑問題" (我亂起的名字).

比如, 樹上是否存在長為 \(k\) 的路徑, 樹上長小於 \(k\) 的路徑有多少條等等.

點分治可以概括為 : 分治 + 重心 + 桶 (就目前我做過的幾道題來說都是這個套路)

我們就直接針對一道題來吧.

詢問樹上距離為 \(k\) 的點對是否存在

換句話說, 就是樹上是否存在長度為 \(k\) 的路徑.

首先, 選乙個根節點, 然後樹上的路徑就被分成了兩類,

經過根節點.

不經過根節點.

考慮怎麼處理第一類路徑.

我們設 \(dis[u]\) 為點 \(u\) 到根節點的距離,

建乙個桶 \(b[i]\), 表示是否存在 \(dis[u] = i\) 的節點.

然後對每一棵子樹都 \(dfs\) 兩遍.

第一遍檢視桶中是否存在 \(dis = k-dis[u]\) 的點, 即 \(b[k-dis[u]]\) 是否為 \(1\).

第二遍把點放進桶中, 即 \(b[dis[u]]=1\).

第一類路徑就這樣處理完了, 時間複雜度為 \(o(n)\).

而對於第二類路徑, 我們可以把它看做經過了另外乙個 "根節點" 的第一類路徑, 然後就可以遞迴完成了.

若遞迴層數為 \(t\), 總時間複雜度就為 \(o(tn)\).

那我們怎麼使 \(t\) 盡量小呢?

答案是------以重心作為根節點.

這還是比較好理解的, 因為重心會將整棵樹分成若干個 \(size \le \frac\) 的子樹, 所以 \(t\) 就是 \(\log n\) 級別的.

這樣, 總複雜度就為 \(o(n \log n)\) 了.

還有乙個小問題, 就是我們在處理第一類路徑中所要使用的桶, 如果每次遞迴都要全部清空會浪費很多時間.

因此, 我們在修改桶的時候, 可以用乙個佇列把修改過的位置記下來, 最後清空的時候只需要把這些位置歸零就行了, 這樣就保證了每一層遞迴的修改操作的時間複雜度為 \(o(n)\).

#includeusing namespace std;

const int _=1e4+7;

const int __=1e7+7;

const int inf=0x3f3f3f3f;

bool st;

int n,m,maxk=10000000,sz[_],dis[_],rt,minx=inf,q[_],top,qes[100+7];

int lst[_],nxt[2*_],to[2*_],len[2*_],tot;

bool vis[_],b[__],ans[100+7];

bool en;

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

void read()

}void mrk(int u,int fa)

for(int i=lst[u];i;i=nxt[i])

}void calc(int u)

for(int i=1;i<=top;i++) b[q[i]]=0;

top=0;

}void run(int u,int lstrt)

calc(u);

vis[u]=0;

}int main()

【模板】點分治

p4178 tree

[ioi2011]race

點分治略解 by dispwnl

學習筆記 點分治學習筆記

點分治學習筆記 oi中有一類在樹上與路徑有關的題目。如果直接列舉兩個端點複雜度至少o n 2 通常無法承受。如果考慮列舉路徑的lca通過一些奇技淫巧計算,通常至少需要dfs一遍子樹,複雜度依舊無法承受。因此可以考慮將所有路徑分為兩類 經過x的和不經過x的。將經過x的路徑全部處理完了之後,相當於是把x...

點分治學習筆記

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

學習筆記 動態點分治

首先要會點分治 點分治有什麼好處?我們為什麼不直接用樹形dp?它多用了乙個logn的代價,使得我們每次面對的都是過重心rt的路徑。這樣,我們可以靈活用子樹來處理。而樹形dp必須一次考慮所有過x的所有路徑。必須還要多處理乙個 和x有關 的資訊,多了o n 的時空。點分治由於同一層不用考慮其他路徑,所以...