Description

给定一颗 \(n\) 个顶点的树 \(\text T\),共 \(n-1\) 次断边操作,每次将树分为两部分 \(\text T_1, \text T_2\),求:

\[\sum_{(u, v)\in\text E}\left(
\sum_{x \text{为} \text T_1 \text{的重心} } x + \sum_{y \text{为} \text T_2 \text{的重心} } y
\right)
\]

注意,一棵树的重心可能有两个。

Hint

\(1\le n\le 3\times 10^5\)

Solution

不会 \(O(n\log n)\) 或 \(O(n\log^2 n)\) 的做法,这里介绍 \(O(n)\) 的做法 头都写掉了。首先考虑重心的一些性质:

  • 一棵树的重心必定存在于 根所在的重链 上。

    • 略证:我们称 重儿子 \(wson(x)\) 为当前根 \(x\) 的子结点中子树大小(\(size\))最大的那一个。如果 \(size(wson(x)) \le \tfrac 1 2\),那么根自己就是重心;如果 \(size(wson(x)) > \tfrac 1 2\),若是选取非重儿子那必然会有一部分的 \(size > \tfrac 1 2\),因此重心必定在重儿子的子树内。
    • 这有什么用呢?每次找重心可以 只在重链上考虑 而不是整棵树。
  • 一颗子树的重心必然为其重儿子子树的重心的一个 祖先
    • 略证:由上一个性质得,重儿子这个重心、当前子树的重心都在当前子树根所在的重链上,那么加上当前子树根其他部分后重心只会向上偏移,即祖先。
    • 这有什么用呢?这意味着在知道重儿子的重心后,我们不难向上调整出当前新的重心,从而做到 \(O(n)\) 求出 所有子树的重心
  • 一颗树的重心如果存在两个,那么它们 必然相邻
    • 略证:考虑这样一个性质:以重心为根的树,其根的所有子树大小 不超过 原树的 \(\tfrac 1 2\)。“不超过”包含“小于”和“等于”。如果是小于自然就只有一个重心;如果等于,如果以将树根换成原来的根的 重儿子,又会出现恰好等于一半的情况,新的重儿子其实就是 原来的根。如此重心只会在这两个顶点上移动,那么它们显然是相邻的。
    • 这有什么用呢?实际在操作是就可以优先找 深度相对较大(若在,转为有根树两个相邻重心必然一父一子)的重心,在判断其父亲是不是即可。注意,下面预处理的重心都是深度大的。

以上性质是下面算法的理论基础。


我们先令原树的一个重心作为根 \(root\),然后看看断掉一条边 \((x, y)\) 后会发生什么(\(x\) 为 \(y\) 的父亲)。

原树 \(\text T\) 分裂为两部分:以 \(y\) 为根的子树 \(\text T^\prime\),以及剩下部分 \(\text T - \text T^\prime\)。

  • 对于 \(\text T^\prime\) 部分:由于上面已经提到了 \(O(n)\) 预处理所有子树重心的方法,那么这就没啥问题。
  • 对于 \(\text T - \text T^\prime\) 部分:对在原树中删去部分的位置分类讨论:
    • 如果 \(\text T^\prime\) 不在根结点所在重链上,那么原来那个重心会在其所在这条重链上移动。可以预处理出一个 \(\text{ans}_1(s)\) 表示删去大小为 \(s\) 的 非根所在重链部分 后,剩余 \(\text T - \text T^\prime\) 部分的重心是哪个。这个就是跑一遍 \(root\) 所在的重链,复杂度 \(O(n)\)。
    • 如果 \(\text T^\prime\) 在根结点所在重链上,那么 \(root\) 的原来那个重儿子不一定还是重儿子了,对此又分两种情况讨论:
      • 如果重儿子没变,那么原来的重心会 上移,而如果重心本来就是根那必然还是这个根,这就是我们将重心设为 \(root\) 的原因。
      • 如果重儿子变了,首先可以肯定的是,新的重儿子就是原先的 次重儿子,原来的重心会转移到次重子树中。于是也可以预处理一个 \(\text{ans}_2(s)\) 表示删去重链上的大小为 \(s\) 的子树后的重心,具体方法和 \(\text{ans}_1\) 类似,只要在 \(root\) 的位置先走次重儿子即可。复杂度显然也是 \(O(n)\)。

总复杂度显然 \(O(n)\),但是带一个大常数。

Code

实现细节非常多,必须保证自己思路清晰。

/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : CSP-S 2019 树的重心
*/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <functional>
#include <vector> const int N = 3e5 + 5;
int n, T;
std::vector<int> adj[N]; int siz[N], wson[N], root;
void findRoot(int x, int f) {
siz[x] = 1, wson[x] = 0;
for (auto y : adj[x]) if (y != f) {
findRoot(y, x), siz[x] += siz[y];
if (siz[wson[x]] < siz[y]) wson[x] = y;
}
if (siz[wson[x]] * 2 <= n && (n - siz[x]) * 2 <= n)
root = x;
} int dep[N], fa[N], sid[N], swson;
void prework(int x, int f) {
siz[x] = 1, dep[x] = dep[fa[x] = f] + 1;
sid[x] = (f == root) ? x : sid[f];
for (auto y : adj[x]) if (y != f) {
prework(y, x), siz[x] += siz[y];
if (siz[wson[x]] < siz[y]) {
if (x == root) swson = wson[x];
wson[x] = y;
} else if (x == root && siz[y] > siz[swson])
swson = y;
}
} int ans1[N], ans2[N], ans3[N], cutSiz;
void calc1(int x, int& cutSiz) {
if (wson[x]) calc1(wson[x], cutSiz);
while (cutSiz && cutSiz >= n - siz[x] * 2)
ans1[cutSiz--] = x;
}
void calc2(int x, int& cutSiz) {
if (x == root) calc2(swson, cutSiz);
else if (wson[x]) calc2(wson[x], cutSiz);
while (cutSiz && cutSiz >= n - siz[x] * 2)
ans2[cutSiz--] = x;
}
void calc3(int x) {
if (wson[x]) calc3(wson[x]);
for (auto y : adj[x]) if (y != fa[x] && y != wson[x]) calc3(y);
ans3[x] = wson[x] ? ans3[wson[x]] : x;
while (siz[ans3[x]] * 2 < siz[x]) ans3[x] = fa[ans3[x]];
} void clear() {
memset(ans1, 0, sizeof(ans1));
memset(ans2, 0, sizeof(ans2));
memset(ans3, 0, sizeof(ans3)); for (int i = 1; i < N; i++) adj[i].clear();
memset(dep, 0, sizeof(dep));
memset(siz, 0, sizeof(siz));
memset(wson, 0, sizeof(wson));
memset(fa, 0, sizeof(fa));
memset(sid, 0, sizeof(sid));
} void solve() {
std::function<bool(int, int)> check[3] = {
[&](int x, int y) {
return x && siz[wson[x]] * 2 <= siz[y]
&& (siz[y] - siz[x]) * 2 <= siz[y];
},
[&](int x, int y) {
if (x == root) return siz[swson] * 2 <= n - siz[y];
return x && siz[wson[x]] * 2 <= n - siz[y]
&& (n - siz[x] - siz[y]) * 2 <= n - siz[y];
},
[&](int x, int y) {
if (x == root) return siz[wson[x]] * 2 <= n - siz[y];
return x && siz[wson[x]] * 2 <= n - siz[y]
&& (n - siz[x] - siz[y]) * 2 <= n - siz[y];
}
}; long long ans = 0;
for (int i = 1; i <= n; i++) for (auto j : adj[i]) if (i < j) {
int x = i, y = j;
if (dep[x] > dep[y]) std::swap(x, y);
int yc = ans3[y]; ans += yc; if (dep[fa[yc]] >= dep[y] && check[0](fa[yc], y))
ans += fa[yc]; if (sid[y] == wson[root]) {
if (siz[wson[root]] - siz[y] >= siz[swson]) {
ans += root;
} else {
int xc = ans2[siz[y]]; ans += xc;
if (check[1](fa[xc], y)) ans += fa[xc];
}
} else {
int xc = ans1[siz[y]]; ans += xc;
if (check[2](fa[xc], y)) ans += fa[xc];
}
}
printf("%lld\n", ans);
} signed main() {
scanf("%d", &T);
while (T--) {
scanf("%d", &n), clear();
for (int i = 1, u, v; i < n; i++) {
scanf("%d%d", &u, &v);
adj[u].emplace_back(v);
adj[v].emplace_back(u);
} root = 0, findRoot(1, 0);
memset(wson, 0, sizeof(wson)), swson = 0, prework(root, 0);
cutSiz = n; calc1(root, cutSiz);
cutSiz = n, calc2(root, cutSiz);
calc3(root), solve();
}
return 0;
}

【CSP-S 2019】树的重心(重心的性质)的更多相关文章

  1. 上午小测3 T1 括号序列 && luogu P5658 [CSP/S 2019 D1T2] 括号树 题解

    前 言: 一直很想写这道括号树..毕竟是在去年折磨了我4个小时的题.... 上午小测3 T1 括号序列 前言: 原来这题是个dp啊...这几天出了好几道dp,我都没看出来,我竟然折磨菜. 考试的时候先 ...

  2. AcWing:143. 最大异或对(01字典树 + 位运算 + 异或性质)

    在给定的N个整数A1,A2……ANA1,A2……AN中选出两个进行xor(异或)运算,得到的结果最大是多少? 输入格式 第一行输入一个整数N. 第二行输入N个整数A1A1-ANAN. 输出格式 输出一 ...

  3. @CSP模拟2019.10.16 - T3@ 垃圾分类

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 为了保护环境,p6pou建设了一个垃圾分类器. 垃圾分类器是一个 ...

  4. CSP/NOIP 2019 游记

    Day0 打牌 Day1 \(T1\) 没开\(ull\), 不知道有几分 \(T2\) \(N^2\)暴力+链, 没搞出树上做法, \(70\)分 \(T3\) 标准\(10\)分( 感觉今年省一稳 ...

  5. 【置顶】CSP/S 2019退役祭

    标题没错,今年就是我的最后一年了. 才高一啊,真不甘心啊. DAY1(之前的看前几篇博客吧) T1 现在没挂 T2 貌似是树形DP,跑到80000的深度时挂了,于是特判了链的情况,大样例过了,现在没挂 ...

  6. 点分治模板(洛谷P4178 Tree)(树分治,树的重心,容斥原理)

    推荐YCB的总结 推荐你谷ysn等巨佬的详细题解 大致流程-- dfs求出当前树的重心 对当前树内经过重心的路径统计答案(一条路径由两条由重心到其它点的子路径合并而成) 容斥减去不合法情况(两条子路径 ...

  7. BZOJ 3510 - 首都 「 $LCT$ 动态维护树的重心」

    这题 FlashHu 的优化思路值得借鉴 前置引理 树中所有点到某个点的距离和中,到重心的距离和是最小的. 把两棵树通过某一点相连得到一颗新的树,新的树的重心必然在连接原来两棵树重心的路径上. 一棵树 ...

  8. poj3107 Godfather 求树的重心

    Description Last years Chicago was full of gangster fights and strange murders. The chief of the pol ...

  9. 【CodeForces】708 C. Centroids 树的重心

    [题目]C. Centroids [题意]给定一棵树,求每个点能否通过 [ 移动一条边使之仍为树 ] 这一操作成为树的重心.n<=4*10^5. [算法]树的重心 [题解]若树存在双重心,则对于 ...

随机推荐

  1. Golang 接口型函数和http.Handler接口

    一.接口型函数 参考Golang必备技巧:接口型函数 1.原始接口实现 type Handler interface { Do(k, v interface{}) } func Each(m map[ ...

  2. pandas.DataFarme内置的绘图功能参数说明

    可视化是数据探索性分析及结果表达的一种非常重要的形式,因此打算写一个python绘图系列,本文是第一篇,先说一下pandas.DataFrame.plot()绘图功能. pandas.DataFram ...

  3. 基于docker部署ceph以及修改docker image

    前言 容器和ceph的结合已经在一些生产环境当中做了尝试,容器的好处就是对运行环境的一个封装,传统的方式是集成为ISO,这个需要一定的维护量,而容器的相关操作会简单很多,也就有了一些尝试,个人觉得如果 ...

  4. 无所不能的Embedding4 - Doc2vec第二弹[skip-thought & tf-Seq2Seq源码解析]

    前一章Doc2Vec里提到,其实Doc2Vec只是通过加入Doc_id捕捉了文本的主题信息,并没有真正考虑语序以及上下文语义,n-gram只能在局部解决这一问题,那么还有别的解决方案么?依旧是通用文本 ...

  5. ubuntu 18.04安装RTL8821CE无线网卡驱动

    疫情期间闲下来无聊,把办公室的旧机器装了ubuntu,但是无法连接无线网. 打开终端 #查看无线网卡信息. -i 是不区分大小写 tjj@ubuntu:~/Documents$ lspci | gre ...

  6. MySQL 5.x乱码问题解决

    MySQL是一款常用的开源数据库软件,但是对于初次使用者好像并不是太友好,MySQL5.x的版本中默认字符集是latin1也就是我们所知道的ISO-8859-1字符集,这个字符集编码并没有包含汉字,所 ...

  7. mybatis 解决属性名和字段名不一致

    1. 数据库中表的设计 2. 实体类 3.mapper映射文件 4. 问题:密码没有获取到 原因:mybatis会根据查询的列名去进行设值 5. 解决列名和属性名不一致的方法 5.1 为列名指定别名, ...

  8. FL studio系列教程(十七):FL Studio走带面板介绍

    FL Studio走带面板主要是用来控制播放.录音以及调整歌曲速度的,除此之外还可以用来选择样本剪辑.下面就来详细地看一下这部分菜单. 1.样本/歌曲模式 样本/歌曲模式主要是用来切换样本和歌曲两种模 ...

  9. Happen-Before规则

  10. 配置jdb

    目录 注:1)查看当前Linux系统是否已经安装java 1.把jdk文件的压缩包拖入虚拟机 2.找到刚刚拖拽的文件 3.在usr/local下创建jdk的文件夹 4.解压jdk的文件,并存放在刚刚创 ...