樹上的有些問題是可以用樹剖或者動態樹解決的,但是他們有一個動同點就是:不連通。
- 比如求u到v的路徑權值和,或者最大值:
u到v可能對應了多個鏈,這多個鏈在對應的數據結構(假設是線段樹)上面對應不同的區間。但是線段樹上這幾個區間的不連續並不影響我們得到答案。
(當然求子樹的信息話是連續區間)
那么如果我們遇到的問題要求區間連續呢,比如求u到v的路徑上點的權值有多少種?如果不連續就得處理鏈與鏈之間的關系。顯然這些點得待在一起,如果樹剖很難維護鏈與鏈之間的關系。
樹分塊,大概有這樣的一些方法:
- 王室聯邦分塊法:可以保證每個塊的大小和直徑都不超過2√N−1,但是不保證塊聯通
- DFS序分塊法:首先是好寫(畢竟轉化成了序列問題),嚴格保證塊大小√N,但是不保證直徑,也不保證聯通。處理子樹信息比較方便
- size分塊:檢查當前節點的父親所在塊的大小,如果小於√N就把當前節點加入進去,不然新開塊。塊大小最壞√N,保證塊內聯通,還保證直徑,多么優美啊可惜不能保證塊個數(一個菊花圖就死了)
王室聯邦分塊,假設每個塊的數量個數為[B,3B].
如何分組可以看,裸題 BZOJ1086 王室聯邦。

#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的大小。
這樣分塊是為了莫隊的排序,而不是預處理保存信息。比如,(u,v) 轉移到(a,b),由於u和a或在一個組里面,即距離不太遠,轉移時間不太大。
例題: Count on a tree II ,SPOJ - COT2,代碼見這里。
求節點u到節點v路徑上節點數值的種類。 此題有很多種牛逼做法,見此處的整理。
對於不要求在線的題,我們可以可以選擇莫隊算法:根據王室聯邦分塊來分塊排序,按順序轉移得到答案,可以參考這里的代碼。