树点分治--求树的重心

引言

在面对一个无根树的时候,我们往往无从下手,便随便挑选一个节点作为根节点进行操作。
这是很不负责的一种行为,因为它可能导致很高的复杂度。比如对于一条链,你选择了两头的结点。。你很有可能面对爆栈的风险(NOIP2016D1T2 = =亲身经历)

那么,我们如果把一个很好看的树,比如一个满二叉树,把这个图看成无根图。
你会发现满二叉树的原本的根节点就是最好的选择:它恰巧在整个图的“中央”。

我们把这种最好的根节点的选择称为树的重心或者树的质点


树的重心:

那么我们如何去求一棵无根树的重心呢?

当然是从重心的性质入手啦!

我们可以认识到,树的重心其实就是在整个图的“中央”,那么这个中央如何进行定义呢?为了保证深度最小(如果你把根节点放在树的重心上,那么它深度当然是最小的啦!)我们可以把深度最小近似看成是子树结点最少。(二者的关系的密切程度随着子树的划分逐渐密切)

即:去掉重心后,使得剩余子树的最多结点数最小。


如何实现?

我们需要保存一个子树最大结点数最小的情况,可以压缩为一个值min

min代表我们找到的最小值,对应一个变量cog为树的重心的编号

现在就是遍历所有的情况了,那么有人心态崩了:我岂不是要对所有的结点来一遍dfs??

但其实不是这样的,我们只需要一次dfs,如果按照楼上的说法,我们解决了太多的重复子问题了。

我们任选一个结点作为本次寻找树的重心的根节点,然后进行一个dfs,我们意识到dfs可以遍历到所有的结点,也就是可以求到所有的划分子树的值。

怎么获取呢?
我们假设当前节点是now,父亲结点是from,now的儿子们是v[]
那么我们可以递归求得v[i]中包含的结点数,而now本身的返回值是sum=v[1]+v[2]+…+v[n]+1
此时我们遗漏了一个子树:那就是以from为根节点的子树!

而这个子树的节点数有点好求:N-sum

N为总结点数,sum为所有儿子的节点数+1

于是我们对比所有的值,取出最大值max

将max与min比较,更新cog

一遍遍历以后我们便取到了树的重心。


代码!(这次我真的不想写注释了)


#include
#define maxn 1005
using namespace std;
struct node{
    int to;
    int next;
}edge[maxn];
int head[maxn],sub_node[maxn],e,n,m,min=maxn,cog=0;
void add(int u,int v){
    edge[++e].next=head[u];
    edge[e].to=v;
    head[u]=e;
    return ;
}
int dfs(int now,int from){
    int sum=0,max=0;
    for(int i=head[now];i!=0;i=edge[i].next){
        int v=edge[i].to;
        if(v==from ) continue;
        sum+=dfs(v,now);
        if(sub_node[v]>max) max=sub_node[v];
    }
    sum++;
    if(max<(n-sum))
        max=n-sum;
    if(maxreturn sum;
}
int main(){
    int u,v;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d %d",&u,&v);
        add(u,v);
        add(v,u);
    }
    int s=dfs(1,0);
    printf("this tree 's center of gravity is :%d\n",cog);
    for(int i=1;i<=n;i++) printf("%d ",sub_node[i]);
    return 0;
}

你可能感兴趣的:(基本算法)