點分治, 其實應該叫 "樹上點分治".
主要用於解決 "樹上路徑問題" (我亂起的名字).
比如, 樹上是否存在長為 \(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 的時空。點分治由於同一層不用考慮其他路徑,所以...