樹分治
一句話講,把分治做到樹上。
樹分治首先要把無根樹轉成有根樹(如果是無根樹,當然有根樹就直接分治),即找一個點 作爲根。
如何找根?
爲了分治的時效,我們需要分治的層數越少越好,於是想讓找到的根下最大子樹的節點越少越好,我們便可以用一趟 來刷。
這裏需要了解幾個數組: 表示 節點下最大子樹的節點數, 表示 節點所在子樹的節點數。
我們在遍歷到 這個點時, , 。因爲用 ,所以逆推就好了。
void getrt(int x,int fa){ //找根
F[x]=0;Size[x]=1; //初始化
for (int i=lnk[x];i;i=nxt[i]){ //遍歷下一個點
if (vis[son[i]]||son[i]==fa) continue; //防止循環
getrt(son[i],x); //dfs下去
Size[x]+=Size[son[i]];
F[x]=max(F[x],Size[son[i]]); //修正
}
F[x]=max(F[x],Sum-Size[x]); //Sum是整棵樹的節點數,也就是Sum=N
if (F[x]<F[Root]) Root=x; //修改Root
}
注意,代碼中有一句F[x]=max(F[x],Sum-Size[x]);
,是因爲 節點沒法遍歷到 節點,但是以 爲根, 節點所在的子樹沒有被統計,所以最後要補上一道。
PS:所求的點又叫重心
正題
現在進入正題——樹分治
我們可以把樹上的每個節點看成一個人,整棵樹看成一個公司,老闆(根節點)有一項任務,於是找來了所有部門,把任務下發;而每個部門又可以看成一棵樹,部長(子樹的根節點)找來了該部門的所有員工,把任務下發,以此類推。這樣就是分治。
那麼接下來就很簡單了。從根節點開始,下面每個節點找一下重心,繼續分治下去即可。
inline void Solve(int x){ //分治
vis[x]=1;
for (int i=lnk[x];i;i=nxt[i]){
if (vis[son[i]]) continue; //已經分過了就不要再分,防止循環
Root=0;Sum=Size[son[i]];
//初始化,這裏Sum=Size[son[i]],是因爲這棵子樹從整棵樹上摘下來,也就是公司不管部門的事
getrt(son[i],0); //找子樹的重心
Solve(Root); //繼續分下去
}
}
好了,沒了,就這麼簡單。
例題
poj1741 Tree
bzoj2152聰聰可可 雙倍經驗
洛谷
還有一種邊分治,但我這種蒟蒻555