樹上的有些問題是能夠用樹剖或者動態樹解決的,可是他們有一個動同點就是:不連通。
- 好比求u到v的路徑權值和,或者最大值:
u到v可能對應了多個鏈,這多個鏈在對應的數據結構(假設是線段樹)上面對應不一樣的區間。可是線段樹上這幾個區間的不連續並不影響咱們獲得答案。html
(固然求子樹的信息話是連續區間)ios
那麼若是咱們遇到的問題要求區間連續呢,好比求u到v的路徑上點的權值有多少種?若是不連續就得處理鏈與鏈之間的關係。顯然這些點得待在一塊兒,若是樹剖很難維護鏈與鏈之間的關係。算法
樹分塊,大概有這樣的一些方法:
- 王室聯邦分塊法:能夠保證每一個塊的大小和直徑都不超過2√N−1,可是不保證塊聯通
- DFS序分塊法:首先是好寫(畢竟轉化成了序列問題),嚴格保證塊大小√N,可是不保證直徑,也不保證聯通。處理子樹信息比較方便
- size分塊:檢查當前節點的父親所在塊的大小,若是小於√N就把當前節點加入進去,否則新開塊。塊大小最壞√N,保證塊內聯通,還保證直徑,多麼優美啊惋惜不能保證塊個數(一個菊花圖就死了)
王室聯邦分塊,假設每一個塊的數量個數爲[B,3B].
如何分組能夠看,裸題 BZOJ1086 王室聯邦。 數據結構
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
#include<cstdio> #include<iostream> #include<algorithm> #include<cstring> using namespace std; int const maxn=2010; int q[maxn],group[maxn],rt[maxn],top; int Laxt[maxn],Next[maxn],To[maxn],cnt,ans; int n,B; void init() { ans=0; top=0; cnt=0; memset(Laxt,0,sizeof(Laxt)); } void add(int u,int v) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; } void dfs(int u,int pre) { int Now=top; for(int i=Laxt[u];i;i=Next[i]){ if(To[i]!=pre){ dfs(To[i],u); if(top-Now>=B) { rt[++ans]=u; while(top!=Now) group[q[top--]]=ans;} } } q[++top]=u; } int main() { while(~scanf("%d%d",&n,&B)){ int u,v; init(); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v);add(v,u); } dfs(1,0); while(top) group[q[top--]]=ans; printf("%d\n",ans); for(int i=1;i<=n;i++) printf("%d ",group[i]);printf("\n"); for(int i=1;i<=ans;i++) printf("%d ",rt[i]);printf("\n"); } return 0; }
再稍微介紹一下王室聯邦分塊是幹嗎的:
咱們dfs,把子樹中大於B的分爲一組,剩餘的(確定小於B)上傳分到父親那組。因爲父親那組大於B,加進去小於3B。每一組即比較平均了,B的大小會影響空間和時間的優劣,須要根據題目給定的時間和空間,時間多空間小就B大,空間多時間少就B小。從而來決定B的大小。ide
這樣分塊是爲了莫隊的排序,而不是預處理保存信息。好比,(u,v) 轉移到(a,b),因爲u和a或在一個組裏面,即距離不太遠,轉移時間不太大。spa
例題: Count on a tree II ,SPOJ - COT2,代碼見這裏。
求節點u到節點v路徑上節點數值的種類。 此題有不少種牛逼作法,見此處的整理。.net
對於不要求在線的題,咱們能夠能夠選擇莫隊算法:根據王室聯邦分塊來分塊排序,按順序轉移獲得答案,能夠參考這裏的代碼。code