【模板】树的重心

树的重心是指使得最大子树结点数最小的点,具有多个性质,例如所有点到重心的距离和最小。在无权树中,以重心为根的所有子树大小不超过一半。本文讨论了如何找到树的重心,包括处理含有两个重心的情况,并提供了Codeforces和ICPC亚洲徐州区域赛的例题分析。

树的重心

树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小。

性质:

  1. 树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个重心,他们的距离和一样。
  2. 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
  3. 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
  4. 一棵树最多有两个重心,且相邻。
  5. 对于无权树,以重心为根,所有子树的大小都不超过整棵树大小的一半,即其所有子树大小 ≤ N / 2 \le N/2 N/2

带权树的重心:https://www.cnblogs.com/knife-rose/p/11258403.html

int T, n;
const int maxn = 1e5+7;
int sz[maxn], hs[maxn];
// sz[i]表示以i为根的子树的大小, hs[i]表示以i为根的重儿子(最大子树)的大小

struct edge{
    int next, v;
}e[maxn*2];
int cnt = 0;
int head[maxn];

int cent[2] = {0, 0}; // 重心
int ccnt = 0; // 重心个数

 
void addedge(int u, int v)
{
    e[cnt] = edge{head[u], v};
    head[u] = cnt++;
    e[cnt] = edge{head[v], u};
    head[v] = cnt++;
}
 
void dfs(int u, int fa)
{
    sz[u] = 1;
    hs[u] = 0;
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v = e[i].v;
        if(v!=fa)
        {
            dfs(v, u);
            sz[u] += sz[v];
            hs[u] = max(hs[u], sz[v]);
            //cout << u << ' ' << v << ' ' << b[u] << endl;
        }
    }
    hs[u] = max(hs[u], n-sz[u]);
    if (hs[u] <= n / 2) {  // 依照树的重心的定义统计
    	cent[ccnt++] = cur;
  }
}

例题

Codeforces-Link Cut Tree
题目给定一棵树,要求删除一个边,再连接一条边,将其变为只有一个重心的树。

若只有一个重心则只需要把任意一个边断开连接一次就行。
若有2个重心分别为A、B
根据性质4:“一棵树最多有两个重心,且相邻
可以想到,将重心A的一个子树与A断开,将该子树连接到重心B上(断开前每个重心所带的子树大小都为N/2,这么操作就会破坏破坏两重心的大小平衡)

#include<bits/stdc++.h>
using namespace std;

int T, n;
const int maxn = 1e5+7;
int a[maxn], b[maxn];

struct edge{
    int next, v;
}e[maxn*2];
int ccnt;
int head[maxn];
int cent[2] = {0, 0};
int cnt = 0, minsub = 0x3f3f3f3f;

void addedge(int u, int v)
{
    e[cnt] = edge{head[u], v};
    head[u] = cnt++;
    e[cnt] = edge{head[v], u};
    head[v] = cnt++;
}

void dfs(int u, int fa)
{
    a[u] = 1;
    b[u] = 0;
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v = e[i].v;
        if(v!=fa)
        {
            dfs(v, u);
            a[u]+=a[v];
            b[u] = max(b[u], a[v]);
            //cout << u << ' ' << v << ' ' << b[u] << endl;
        }
    }
    b[u] = max(b[u], n-a[u]);
    if(b[u]<=n/2)
        cent[ccnt++] = u;
}


int main()
{
    cin >> T;
    while(T--)
    {
        cin >> n;
        cent[0] = cent[1] = 0;
        minsub = 0x3f3f3f3f;
        memset(head, -1, sizeof head);
        int u, v;
        for(int i=0;i<n-1;i++)
        {
            cin >> u >> v;
            addedge(u, v);
        }
        ccnt = 0;
        dfs(1, 0);
        
        
        if(ccnt==2){
            int v = e[head[cent[0]]].v;
            if(v==cent[1])
            {
                v = e[e[head[cent[0]]].next].v;
            }
            cout << cent[0] << ' ' << v << endl;
            cout << v << ' ' << cent[1] << endl;
        }
        else{
            cout << 1 << ' ' << e[head[1]].v << endl;
            cout << 1 << ' ' << e[head[1]].v << endl;
        }
//        cout << ccnt << endl;
//        cout << minsub << endl;
//        cout << cent[0] << ' ' << cent[1] << endl;
    }
    return 0;
}

求所有子树的重心

根据 性质5: 对于无权树,以重心为根,所有子树的大小都不超过整棵树大小的一半,即其所有子树大小 ≤ N / 2 \le N/2 N/2

根据这个重心的定义可以知道一棵子树的重心必定在他自己所在的重子树中. 所以每次找到他的重儿子为根的子树的重心, 不符合的话就沿着重链往上走直至找到符合要求( s i z e [ h s [ p ] ] < = s i z e [ x ] / 2 size[hs[p]] <= size[x]/2 size[hs[p]]<=size[x]/2 以及 s i z e [ x ] − s i z e [ p ] < = s i z e [ x ] / 2 size[x]-size[p] <= size[x]/2 size[x]size[p]<=size[x]/2)的重心.
(hs[x]表示x的重儿子)

在x子树上对于p节点是否为重心的检查

例题:
2019 ICPC Asia Xuzhou Regional M - Kill the tree
题目要求找出所有子树的重心。两个重心的要按编号顺序输出。
对于两个重心的子树,因为我们是从底向上寻找第一个符合条件的重心就跳出,所以我们先找到深度较大的重心。根据性质4,可得两个重心是相邻的,所以第二个重心一定是这个重心的父节点,我们在输出时需要判断一下其父节点是否也符合重心的性质。

#include<bits/stdc++.h>
#define ms(a,v) memset((a), (v), sizeof(a));
#define rep(i,l,u) for(int (i)=(l);(i)<=(u);(i)++)
using namespace std;
#define ll long long
#define pr pair<int, int>
const ll maxn = 2e5+10;
const int mod = 1000000007;
struct edge{
    int v, next;
}e[maxn*2];

int head[maxn], ecnt;
int n;

int fa[maxn], sz[maxn], cent[maxn][2], hs[maxn], csz[maxn];

void add(int u, int v)
{
    e[ecnt].v = v;
    e[ecnt].next = head[u];
    head[u] = ecnt++;
}

void dfs(int x, int pre)
{
    fa[x] = pre;
    sz[x] = 1;
    cent[x][0] = x;
    int pson = x, maxx = 0;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int v = e[i].v;
        if(v!=pre){
            dfs(v, x);
            if(sz[v] > maxx)
            {
                maxx = sz[v];
                pson = v;
            }
            sz[x] += sz[v];
        }
    }
    hs[x] = pson;
    if(pson == x) {
        csz[x] = 1;
        return;
    }
    int p = cent[pson][0];
    // cout << pson << endl;
    while(p != fa[x])
    {
        if(sz[hs[p]]*2<=sz[x]&&(sz[x]-sz[p])*2<=sz[x]){
            cent[x][csz[x]++] = p;
            break;
        }
        p = fa[p];
    }
}



int main()
{
    cin >> n;
    int u, v;
    for(int i=1;i<=n;i++)
        head[i] = -1;
    
    for(int i=0;i<n-1;i++)
    {
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs(1, 0);
    for(int i=1;i<=n;i++){
        if(fa[cent[i][0]] && sz[i] == 2 * sz[cent[i][0]])
            cent[i][csz[i]++] = fa[cent[i][0]];
        if(csz[i]==1)
            printf("%d\n", cent[i][0]);
        else{
            sort(cent[i], cent[i]+2);
            printf("%d %d\n", cent[i][0], cent[i][1]);
        }
    }
    return 0;
}
### 关于AcWing平台上的树的重心 #### 树的重心概念 在图论中,是一种特殊的无向图结构。对于一棵有点权或边权的而言,其重定义如下:设某结点为v,则以v为根时的最大子大小最小化。具体来说,在删除一个节点及其相连的所有边之后,原会分裂成若干棵不相交的小;这些小中的最大者所含有的节点数目即为该节点作为分割点后的“重量”。而整棵树的重心是指能使上述提到的最大子规模达到全局极小值的那个特殊位置。 #### AcWing上关于树的重心的相关题目 - **模板题 AcWing 846. 树的重心**[^1] 此问题要求计算给定的一棵内部所有的重,并输出它们的位置以及对应的最小最大子尺寸。这类问题是典型的基于深度优先搜索(DFS)的应用场景之一,通过递归方式遍历整张网络并记录下必要的统计信息来完成求解过程。 ```python from collections import defaultdict, deque def find_centroid(n, edges): adj = defaultdict(list) for u, v in edges: adj[u].append(v) adj[v].append(u) size = [0] * (n + 1) centroid = [] def dfs(node, parent=None): subtree_size = 1 iscentroid = True for neighbor in adj[node]: if neighbor != parent: child_subtree_size = dfs(neighbor, node) if child_subtree_size > n // 2 or \ (parent and child_subtree_size >= n / 2): iscentroid = False subtree_size += child_subtree_size size[node] = subtree_size if not parent and max(subtree_size, n - subtree_size) <= n // 2: iscentroid = True if iscentroid: centroid.append((node, max(size[node], n-size[node]))) return subtree_size dfs(1) return min(centroid, key=lambda x:x[1]) if centroid else None ``` 这段代码实现了寻找单颗无根内所有可能成为重的候选节点的功能。它利用了两次深度优先搜索策略:第一次用于构建辅助数组`size[]`保存各节点对应子内的总节点数;第二次则依据之前得到的信息进一步筛选符合条件的最佳选项。 #### 教程资源推荐 为了更好地理解和掌握如何解决此类竞赛编程挑战,建议参考以下资料深入学习: - 【AcWing 学习】图论与搜索章节下的相关内容[^2] 这里提供了详细的理论讲解和技术要点分析,帮助理解不同类型的图结构特性及相应的算法实现技巧。特别是针对像这样的特殊案例进行了重点剖析,非常适合初学者逐步建立起扎实的知识体系框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值