首页 > 其他分享 >树的重心

树的重心

时间:2024-11-21 20:56:22浏览次数:1  
标签:sz 子树 重心 int ll fa

定义 1:删去该点后最大子树最小的点
定义 2:删去该点后所有子树大小均不超过 n/2 的点

两个定义是等价的。如果一个点有超过 n/2 的子树,那么往这个方向走一步,其最大子树会变小。

性质:

  • 一棵树最多有 2 个重心且相邻
  • 重心到所有点距离和最小
  • 可以用调整法证明(相当于换根),P2986 [USACO10MAR] Great Cow Gathering G 这题奶牛的集会地点相当于在带权重心

例题:P5666 [CSP-S2019] 树的重心

分析:对于 \(40\%\) 的数据,枚举删除的边,求两棵树的重心即可,时间复杂度为 \(O(n^2)\)。

对于链的情况,每棵树重心一定是链中点,枚举删除的边后可以 \(O(1)\) 计算重心,总时间复杂度为 \(O(n)\)。

对于完美二叉树的情况,重心可以直接分析:

image

参考代码
#include <cstdio>
#include <vector>
using std::vector;
using ll = long long;
const int N = 300005;
const ll INF = 1e18;
vector<int> tree[N];
int sz[N], chain[N], idx, c1, c2;
ll minsum;
bool perfect;
ll dfs(int u, int fa, int d) {
    sz[u] = 1;
    ll res = d;
    for (int v : tree[u]) {
        if (v == fa) continue;
        res += dfs(v, u, d + 1);
        sz[u] += sz[v];
    }
    return res;
}
void calc(int u, int fa, ll sum, int n) {
    if (sum < minsum) {
        minsum = sum; c1 = u; c2 = 0;
    } else if (sum == minsum) {
        c2 = u;
    }
    for (int v : tree[u]) {
        if (v == fa) continue;
        calc(v, u, sum + n - 2 * sz[v], n);
    }
}
bool check_chain(int n) {
    for (int i = 1; i <= n; i++) 
        if (tree[i].size() > 2) return false;
    return true;
}
void dfs_chain(int u, int fa) {
    chain[++idx] = u;
    for (int v : tree[u]) {
        if (v == fa) continue;
        dfs_chain(v, u);
    }
}
int getcenter(int l, int r) {
    int s = l + r;
    if (s % 2 == 0) return chain[s / 2];
    else return chain[s / 2] + chain[s / 2 + 1];
}
int check_perfect_size(int u, int fa, int correct_size) {
    int sz = 1;
    for (int v : tree[u]) {
        if (v == fa) continue;
        sz += check_perfect_size(v, u, correct_size / 2);
    }
    if (sz != correct_size) perfect = false;
    return sz;
}
bool check_perfect(int n) {
    int root = 0;
    for (int i = 1; i <= n; i++) 
        if (tree[i].size() == 2) {
            if (root != 0) return false;
            root = i;
        }
    perfect = true;
    check_perfect_size(root, 0, n);
    return perfect;
}
void solve() {
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; i++) tree[i].clear();
    for (int i = 1; i < n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        tree[u].push_back(v);
        tree[v].push_back(u);
    }

    ll ans = 0;
    if (check_chain(n)) {
        for (int i = 1; i <= n; i++) {
            if (tree[i].size() == 1) {
                idx = 0; dfs_chain(i, 0); break;
            }
        }
        for (int i = 1; i < n; i++) {
            // delete the edge (chain[i], chain[i+1])
            ans += getcenter(1, i);
            ans += getcenter(i + 1, n);
        }
        printf("%lld\n", ans);
    } else if (check_perfect(n)) {
        int root = 1;
        ll ans = 1ll * n * (n + 1) / 2;
        for (int i = 1; i <= n; i++)    
            if (tree[i].size() == 2) {
                root = i; break;
            }
        ans -= root;
        ans += 1ll * (n - 1) / 2 * tree[root][0];
        ans += 1ll * (n - 1) / 2 * tree[root][1];
        ans += 1ll * (n + 1) / 2 * root;
        printf("%lld\n", ans);
    } else {
        for (int u = 1; u <= n; u++) {
            for (int v : tree[u]) {
                // delete the edge (u,v)
                ll sum1 = dfs(u, v, 0), sum2 = dfs(v, u, 0);
                minsum = INF; c1 = u; c2 = 0; calc(u, v, sum1, sz[u]); ans += c1 + c2;
                minsum = INF; c1 = v; c2 = 0; calc(v, u, sum2, sz[v]); ans += c1 + c2;
            }
        }
        printf("%lld\n", ans / 2);
    }
    
}
int main()
{
    int t; scanf("%d", &t);
    for (int i = 1; i <= t; i++) {
        solve();
    }
    return 0;
}

对于一般情况,可以考虑每个点作为重心的贡献。

首先拿出整棵树的一个重心作为根节点 \(root\)。

对于一个不为 \(root\) 的点 \(x\),如果它是删边后某棵树的重心,那么删的边肯定不在 \(x\) 的子树里,否则 \(x\) 向父节点方向发展的子树还是会保持超过 \(n/2\),\(x\) 不可能是重心。

设在 \(x\) 子树外割掉的是一个大小为 \(S\) 的部分,设 \(g_x\) 表示 \(x\) 向下的子树中最大的那棵的大小,则 \(x\) 要做删边后的重心必须满足 \(2 \times (n - S - sz_x) \le n - S\) 并且 \(2 \times g_x \le n - S\)。

即 \(n - 2 \times sz_x \le S \le n - 2 \times g_x\),其中 \(sz_x\) 和 \(g_x\) 可以在求初始重心的 DFS 过程中求出。

对于符合条件的 \(S\) 的数量,可以使用树状数组维护,当根从 \(u\) 换到 \(v\) 时,只需将 \(sz_u\) 处减 \(1\),将 \(n - sz_v\) 处加 \(1\),那符合条件的数量就是一个区间求和了。

但这个是包含子树内的贡献的,想要去掉可以再用一个树状数组, 按 DFS 的顺序插入每个 \(sz_u\),那么进入 \(u\) 时和回溯离开时的差值就是子树内的贡献,所以可以在进入时把答案加上这时该查询区间的结果,在回溯离开时减去那时该查询区间的结果,这样就相当于减去了整棵子树内的贡献。

接下来只差 \(root\) 本身的贡献还没计算。

对于 \(root\),如果删的边不再其最大子树中,显然这时 \(root\) 的最大子树还是原来的最大子树,那就需要两倍的这个最大子树大小 \(\le n - S\)。

否则最大子树就被破坏了,此时只需要满足原来的次大子树的两倍大小 \(\le n - S\)。

所以可以先求出最大子树和次大子树对应节点,进行 DFS,考虑删除每一条边的情况,分两种情况查询结果即可。

这样答案就全部统计完成了。

参考代码
#include <cstdio>
#include <vector>
#include <algorithm>
using std::vector;
using std::max;
using ll = long long;
const int N = 300005;
vector<int> tree[N];
int center, n, sz[N], g[N], max1, max2;
ll ans;
bool flag[N];
struct BIT {
    ll c[N];
    void clear(int n) {
        for (int i = 0; i <= n; i++) c[i] = 0;
    }
    int lowbit(int x) {
        return x & -x;
    }    
    void add(int x, int delta) {
        while (x <= n) {
            c[x] += delta;
            x += lowbit(x);
        }
    }
    ll query(int x) {
        ll res = 0;
        while (x > 0) {
            res += c[x];
            x -= lowbit(x);
        }
        return res;
    }
};
BIT bit1, bit2;

void dfs1(int u, int fa) { // 预处理重心、每棵子树大小、每个点下方最大子树大小
    sz[u] = 1; g[u] = 0;
    for (int v : tree[u]) {
        if (v == fa) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[v] > g[u]) g[u] = sz[v];
    }
    if (max(g[u], n - sz[u]) <= n / 2 && center == 0) {
        center = u;
    }
}
void dfs2(int u, int fa) { // 考虑么个点作为重心的贡献
    if (u != center) {
        ans += 1ll * u * (bit1.query(n - 2 * g[u]) - bit1.query(n - 2 * sz[u] - 1));
        // 减去子树下的贡献:先加上此时的查询结果
        ans += 1ll * u * (bit2.query(n - 2 * g[u]) - bit2.query(n - 2 * sz[u] - 1));
    }
    bit2.add(sz[u], 1);
    for (int v : tree[u]) {
        if (v == fa) continue;
        // 换根
        bit1.add(sz[u], -1); bit1.add(n - sz[v], 1);
        if (flag[u]) flag[v] = true; 
        // 根据此时是否在最大子树分两种情况查询结果
        if (2 * sz[flag[v] ? max2 : max1] <= n - sz[v]) ans += center;
        dfs2(v, u);
        bit1.add(sz[u], 1); bit1.add(n - sz[v], -1);
    }
    if (u != center) {
        // 减去子树下的贡献:回溯时减去此时的查询结果
        ans -= 1ll * u * (bit2.query(n - 2 * g[u]) - bit2.query(n - 2 * sz[u] - 1));
    }
}
void solve() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        tree[i].clear();
    }
    for (int i = 1; i < n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        tree[u].push_back(v); tree[v].push_back(u);
    }
    center = 0;
    dfs1(1, 0);
    // center是整棵树的重心
    dfs1(center, 0);
    bit1.clear(n); bit2.clear(n); 
    for (int i = 1; i <= n; i++) { // 树状数组维护每个可以割的大小S
        bit1.add(sz[i], 1); flag[i] = false;
    }
    ans = 0;
    max1 = max2 = 0; // 根节点的最大、次大子树
    for (int v : tree[center]) {
        if (max1 == 0 || sz[v] > sz[max1]) {
            max2 = max1; max1 = v;
        } else if (max2 == 0 || sz[v] > sz[max2]) {
            max2 = v;
        }
    }
    flag[max1] = true;
    dfs2(center, 0);
    printf("%lld\n", ans);
}
int main()
{
    int t; scanf("%d", &t);
    for (int i = 1; i <= t; i++) solve();
    return 0;
}

标签:sz,子树,重心,int,ll,fa
From: https://www.cnblogs.com/ronchen/p/18206840

相关文章

  • 10.重心与吊装
    重心与起重作业的关系重心就是物体分割成许多微小体积的重力的合力中心。重心与起重作业的安全关系重大,在起重安全施工规范中明确地规定:吊钩作用线与重物的重心必须在同一条垂直线上。物体的重心位置是进行起重施工所必须掌握的重要技术数据之一。重心与起重作业的关......
  • 树的重心
    什么是树的重心?树上选取一个点,使得最大的子树大小最小的点叫做重心。重心有很多优美的性质,求重心是容易的,不再阐述。1.以重心为树根时,最大的子树的大小不超过全树大小的一半,同时条件是充要的对于充分性:考虑调整法。不妨现在钦定一个重心\(u\)作为树根,有一个儿子\(v\)且......
  • 信息学奥赛初赛天天练-91-CSP-S2023基础题3-编译命令、树的重心、拓扑排序、进制转换
    PDF文档公众号回复关键字:202409172023CSP-S选择题1单项选择题(共15题,每题2分,共计30分:每题有且仅有一个正确选项)11以下哪个命令,能将一个名为main.cpp的C++源文件,编译并生成一个名为main的可执行文件?()Ag++-omainmain.cppBg++-omain.cppmainCg++......
  • 19031 树的重心
    ###思路1.使用DFS遍历树,计算每个节点的子树大小。2.计算每个节点的最大连通块大小。3.找到最大连通块大小最小的节点,即为树的重心。###伪代码1.读取输入数据,构建树的邻接表。2.定义DFS函数,计算每个节点的子树大小。3.遍历所有节点,计算每个节点的最大连通块大小......
  • 树的重心
    树的重心性质:一个点是重心,等价于,以这个点为根,它的每个子树的大小,都不会超过整个树大小的一半(充要条件)性质及其证明POJ3107模板这题卡vector注意判断数组越界voiddfs(inti,intfa){ siz[i]=1; inttmp=0; for(intj=head[i];~j;j=e[j].next){ intv=e[j].to; if(v!......
  • 通达信心理重心战术副图指标公式源码
    {通达信心理重心战术副图指标公式源码}N:=12;{参数可以自己调整}stICKLINE(1,100,100,10,0),COLOR0099FF;STICKLINE(1,0,0,10,0),COLOR0099FF;STICKLINE(1,80,80,1.5,0),COLORYELLOW;STICKLINE(1,20,20,1.5,0),COLORYELLOW;STICKLINE(1,50,50,0.7,0),COLORWHITE;MID:=(......
  • 中心 重心
    中心重心重心 和 中心 是两个不同的概念,但在某些情况下可以互换使用。以下是它们的定义和使用场景:重心:在数学和物理学中,重心是指一个图形或物体的几何中心点,它位于所有边或面(如果有的话)的质心位置。对于三角形来说,重心是位于三条边上的中线交点。在更复杂......
  • 重心的意思是指代事、物的核心或主要部分。
    重心的意思是指代事、物的核心或主要部分。一、重心的多种释义1、在日常语言中重心通常用来指代事物的核心或主要部分,例如“工作的重心”或“问题的重心”。2、在物理上重心是指物体各部分所受重力的合力的作用点。质量分布均匀的物体(均匀物体),重心的位置只跟物体的形状有关。......
  • 重心法判断点是否在三角形内
    1)点在三角形的边上时AP=AE+AF(向量加法)设AE=v*AB,AF=u*AC; 则AP=v*AB+u*AC(二元一次方程,u,v为我们引入的变量)根据向量三点共线定理可知:u+v=1 2) 点在三角形内时AE不变, 让AF变短一些, 当用u*AC表示AF时,u的值肯定也比1)中小了,所以此时u+v<1 所以点是否在三......
  • 数学基础:三角形重心坐标插值公式的证明
    在快速Phong明暗处理(Blinn-Phong明暗处理)时,出现了三角形重心坐标插值公式,但没有给出证明.网上也鲜有证明过程,这里给出证明.问题描述:在三角形ABC中,三顶点A、B、C坐标分别为\((x_1,y_1,z_1)、(x_2,y_2,z_2)、(x_3,y_3,z_3)\).则三角形内任一点P(x,y,z)可表示为:\[\tag{1}P=\alph......