树分治
一句话讲,把分治做到树上。
树分治首先要把无根树转成有根树(如果是无根树,当然有根树就直接分治),即找一个点 Root R o o t 作为根。
如何找根?
为了分治的时效,我们需要分治的层数越少越好,于是想让找到的根下最大子树的节点越少越好,我们便可以用一趟
dfs
d
f
s
来刷。
这里需要了解几个数组:
Fi
F
i
表示
i
i
节点下最大子树的节点数,表示
i
i
节点所在子树的节点数。
我们在遍历到这个点时,
Sizefaj+=Sizej
S
i
z
e
f
a
j
+
=
S
i
z
e
j
,
Ffaj=max{Sizej}
F
f
a
j
=
m
a
x
{
S
i
z
e
j
}
。因为用
dfs
d
f
s
,所以逆推就好了。
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]);
,是因为
x
x
节点没法遍历到节点,但是以
x
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