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. 《.NET 5.0 背锅案》第4集:一个.NET,两手准备,一个issue,加倍关注

    第1集:验证 .NET 5.0 正式版 docker 镜像问题 第2集:码中的小窟窿,背后的大坑,发现重要嫌犯 EnyimMemcachedCore 第3集-剧情反转:EnyimMemcachedCo ...

  2. Nacos服务发现源码解析

    1.Spring服务发现的统一规范 Spring将这套规范定义在Spring Cloud Common中 discovery包下面定义了服务发现的规范 核心接口:DiscoveryClient 用于服 ...

  3. Linux(CentOS6.8)配置Docker

    Centos6.8 1.查看自己的内核 [1].uname [root@host79 ~]# uname -r 2.6.32-642.el6.x86_64 [2].查看CentOS版本信息 CentO ...

  4. day95:flask:SQLAlchemy数据库查询进阶&关联查询

    目录 1.数据库查询-进阶 1.常用的SQLAlchemy查询过滤器 2.常用的SQLAlchemy查询结果的方法 3.filter 4.order_by 5.count 6.limit&of ...

  5. ELK---- Elasticsearch 使用ik中文分词器增加拓展热词

    进入到我们ik分词器安装目录下的config目录 cd /usr/local/myapp/elasticsearch-6.4.3/plugins/ik/configvi IKAnalyzer.cfg. ...

  6. django基本内容

    1,流程 1.1 了解web程序工作流程 1.2 django生命周期 2,django介绍 ​ 目的:了解Django框架的作用和特点 ​ 作用: 简便.快速的开发数据库驱动的网站 django的优 ...

  7. C语言是如何诞生的?地位怎样?未来发展趋势?

      C语言的历史 C语言的原型是A语言(ALGOL 60语言). 1963年,剑桥大学将ALGOL 60语言发展成为CPL(Combined Programming Language)语言. 1967 ...

  8. Kafka入门之consumer

    offset存放在_consumer_offsets这个topic下 并且从0-49划分了50个分区: consumer会在kafka集群的所有broker中选择一个broker作为consumer ...

  9. linux上的PXE装机服务的搭建

    PXE 先安装一下依赖服务 yum -y install vsftpd dhcp tftp syslinux tftp-server cd /var/ftp/pub/ mkdir dvd 设置权限 c ...

  10. Arcgis基于高程(DEM)计算地形湿度指数(TWI),以及坡度(Slope)度单位转换为弧度

    以30m*30m分辨率的图层为例 一.基于表面工具箱Surface计算Slope 1.如下图输入图层DEM,输出Slope 2.单位转换: Scale_slope=Slope*pi/180 二.基于水 ...