树的重心

转载,仅作笔记用处 https://blog.csdn.net/zstuyyyyccccbbbb/article/details/108558682
树的重心有以下几条性质
树的重心定义为树的某个节点,当去掉该节点后,树的各个连通分量中,节点数最多的连通分量其节点数达到最小值。树可能存在多个重心。如下图(自制),当去掉点1后,树将分成两个连通块:(2,4,5),(3,6,7),则最大的连通块包含节点个数为3。若去掉点2,则树将分成3个部分,(4),(5),(1,3,6,7)最大的连通块包含4个节点;第一种方法可以得到更小的最大联通分量。可以发现,其他方案不可能得到比3更小的值了。所以,点1是树的重心。

在这里插入图片描述
1.删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心;

2.树中所有节点到重心的距离之和最小,如果有两个重心,那么他们距离之和相等;

3.两个树通过一条边合并,新的重心在原树两个重心的路径上;

4.树删除或添加一个叶子节点,重心最多只移动一条边;

5.一颗树最多只有两个重心且相邻

模板题:https://vjudge.net/problem/CodeForces-1406C

思路:如果找到只有一个重心,那么直接删一个重心的直连边然后加回去就好了。

如果找到两个重心,那么在其中一个重心上找到一个直连点不是另一个重心,删除连另外一个就好了。

如何求树的重心?,

先任选一个结点作为根节点,把无根树变成有根树。然后设siz[i]表示以i为根节点的子树节点个数。转移为siz[i]=∑(siz[i的儿子节点] );

设son[i]表示删去节点x后剩下的连通分量中最大子树节点个数。其中一部分在原来i其为根的子树。son[i]=max(son[i],siz[i的儿子节点]);

另外一部分在i的“上方”子树有n-siz[i]个。 son[i]=max(son[i],n-siz[i]);

在这里插入图片描述

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstring>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <ctype.h>
#include <deque>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int mod=998244353;
const int inf=99999999;
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS ios::sync_with_stdio(false);cin.tie(0)
#define mcy(a,b) memcpy(a,b,sizeof(a))
const int N=1e5+7;
vector<int>g[N];
int siz[N];
int n;
int r1,r2;
void dfs(int u,int f)
{
    siz[u]=1;
    int mxp=0;
    for(int i=0;i<g[u].size();++i)
    {
        int v=g[u][i];
        if(v==f)continue;
        dfs(v,u);
        siz[u]+=siz[v];//size指的是u作为父节点时子节点的个数
        mxp=max(mxp,siz[v]);//mxp是指删掉u节点之后的最大连通分量(块),这个值可能是子节点个数

    }
    mxp=max(mxp,n-siz[u]);//这个值也可能是除u和u的所有子节点外的节点个数
    if(mxp*2<=n)//如果删掉这个节点的最大连通块小于整个树节点的1/2,那么他就是重心
    {
        r2=r1;
        r1=u;
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        mem(siz,0);
        scanf("%d",&n);
        int u,v;
        for(int i=1;i<=n;++i)g[i].clear();
        for(int i=1;i<n;++i)
        {
            scanf("%d %d",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        r1=r2=0;
        dfs(1,0);
        if(r2==0)//只有r1一个是重心,则直接删除跟重心相连的一条边,并且重新加回去就可以了(r1是重心的坐标)
        {
            int r3 = g[r1][0]; //r3是跟r1相连的另一个点的坐标
            cout << r1 << " " << r3 << endl;
            cout << r1 << " " << r3 << endl;
        }else      //如果有两个重心,那么在其中一个重心上找到一个与这个重心直接相连的点且不是另外一个重心,删除这个点与重心之间的边,将这个点连上另外一个重心就好了
        {
            int r3=r1;
            for(int i=0;i<g[r2].size();i++)
            {
                r3 = g[r2][i];
                if(r3!=r1) break;
            }
            cout << r3 << " " << r2 << endl;
            cout << r3 << " " << r1 << endl;
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值