显然如果我们直接枚举断哪条边,剩下的两颗树的重心编号非常不好求,一个常见的想法是我们反过来,考虑每个节点有多少种情况作为树的重心,计算每个点对答案的贡献。

下面我们就需要大力分类讨论了。假设我们现在考虑计算贡献的节点为 \(x\),令以其儿子为根的子树树的最大的子树大小为 \(mx_x\),次大值为 \(se_x\),以 \(x\) 为根的子树大小为 \(S_x\),假设 \(x\) 的一个祖先 \(f\) 断开了其与父亲的边。那么如果 \(x\) 要作为以 \(f\) 为根的树的重心,那么需要满足下面一个条件:

\[\begin{cases}
S_f - S_x \le \lfloor \frac{S_f}{2} \rfloor\\
mx_x \le \lfloor \frac{S_f}{2} \rfloor
\end{cases}\\\]

因为我们需要统计的是满足条件的 \(y\) 的形式,因此我们尽量将原式化简成关于 \(y\) 的一个范围。实际上,上面那个柿子能改写成 \(2 \times S_f - 2 \times S_x \le S_f\),移项可得 \(S_f \le 2 \times S_x\),类似地下面那个柿子有 \(S_f \ge 2 \times mx_x\),于是我们只需要统计一个点 \(x\) 到根的路径上有多少个点满足 \(2 \times mx_x \le S_f \le 2 \times S_x\) 即可。这个我们可以使用树状数组来做,进入每个点是将 \(S_x\) 加入树状数组,出去时删除,那么递归到每个点时的树状数组就是加入 \(x\) 到根路径上点的树状数组了,如果硬核数据结构也可以用主席树实现。

再考虑第二种情况,断掉 \(x\) 子树内的一个点 \(y\) 与其父亲的连边的情况。又要分两种情况,当 \(y\) 在 \(x\) 的重儿子内时,需要满足:

\[\begin{cases}
se_x \le \lfloor \frac{S_1 - S_y}{2} \rfloor\\
mx_x - S_y \le \lfloor \frac{S_1 - S_y}{2} \rfloor\\
S_1 - S_x \le \lfloor \frac{S_1 - S_y}{2} \rfloor
\end{cases}\]

同理上面的化简可得:\(2 \times mx_x - S_1 \le S_y \le \min\{S_1 - 2 \times se_x, 2 \times S_x - S_1\}\),实际上这里我们需要的是统计子树内权值在某个范围的数量,可以使用线段树合并解决。

再考虑如果 \(y\) 在 \(x\) 的非重儿子内部,那么我们需要满足:

\[\begin{cases}
mx_x \le \lfloor \frac{S_1 - S_y}{2} \rfloor\\
S_1 - S_x \le \lfloor \frac{S_1 - S_y}{2} \rfloor
\end{cases}\]

可以得出 \(S_y \le \min\{S_1 - 2 \times mx_x, 2 \times S_x - S_1\}\),同样是查询子树信息,用线段树合并解决。

我们再考虑最后一种情况,当不为 \(x\) 的祖先且不在 \(x\) 子树内的一个点 \(y\) 断掉了其与父亲的连边时,需要满足:

\[\begin{cases}
S_1 - S_y - S_x \le \lfloor \frac{S_1 - S_y}{2} \rfloor\\
mx_x \le \lfloor \frac{S_1 - S_y}{2} \rfloor
\end{cases}\]

化简可得:\(S_1 - 2 \times S_x \le S_y \le S_1 - 2 \times mx_x\)。

这个时候我被难住了,我们怎么知道不包含 \(x\) 的祖先和其子树内的点满足条件的所有 \(y\) 呢?其实很简单,上面我们已经知道了怎么统计在 \(x\) 的子树和其子树内满足条件的方法了,我们直接考虑容斥即可。考虑使用全局满足条件的点数减去在 \(x\) 的祖先和其子树内满足条件的点数即可,这个全局满足条件的点可以使用 \(ton\) 求出。

代码细节比较多,想清楚再码。

#include<bits/stdc++.h>
using namespace std;
#define N 300000 + 5
#define M 6000000 + 5
#define ls t[p].l
#define rs t[p].r
#define mid (l + r) / 2
#define lowbit(x) (x & (-x))
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define Next(i, u) for(int i = h[u]; i; i = e[i].next)
struct edge{
int v, next;
}e[N << 1];
struct tree{
int l, r, sum;
}t[M];
long long ans;
int T, n, u, v, tot, cnt, h[N], c[N], s[N], rt[N], mx[N], se[N], ton[N];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
void Add(int u, int v){
e[++cnt].v = v, e[cnt].next = h[u], h[u] = cnt;
e[++cnt].v = u, e[cnt].next = h[v], h[v] = cnt;
}
void add(int p, int k){
for(int i = p; i <= n; i += lowbit(i)) c[i] += k;
}
int ask(int p){
int ans = 0; p = min(p, n);
for(int i = p; i >= 1; i -= lowbit(i)) ans += c[i];
return ans;
}
void update(int &p, int l, int r, int x, int y, int k){
if(!p) p = ++tot; t[p].sum += k;
if(l == r) return;
if(mid >= x) update(ls, l, mid, x, y, k);
if(mid < y) update(rs, mid + 1, r, x, y, k);
}
int query(int p, int l, int r, int x, int y){
if(!p || l > r) return 0;
if(l >= x && r <= y) return t[p].sum;
int ans = 0;
if(mid >= x) ans += query(ls, l, mid, x, y);
if(mid < y) ans += query(rs, mid + 1, r, x, y);
return ans;
}
void Merge(int &p, int k, int l, int r){
if(!p || !k){ p = p + k; return;}
if(l == r){ t[p].sum += t[k].sum; return;}
Merge(ls, t[k].l, l, mid), Merge(rs, t[k].r, mid + 1, r);
t[p].sum = t[ls].sum + t[rs].sum;
}
void dfs1(int u, int fa){
s[u] = 1;
Next(i, u){
int v = e[i].v; if(v == fa) continue;
dfs1(v, u), s[u] += s[v];
if(s[v] >= mx[u]) se[u] = mx[u], mx[u] = s[v];
else if(s[v] > se[u]) se[u] = s[v];
}
}
void dfs2(int u, int fa){
int tmp = ask(2 * s[u]) - ask(2 * mx[u] - 1) - (ask(s[1] - 2 * mx[u]) - ask(s[1] - 2 * s[u] - 1));
if(mx[u] <= s[u] / 2 && u != 1) ++tmp;
if(s[1] >= 2 * mx[u] && s[1] <= 2 * s[u] && u != 1) --tmp;
add(s[u], 1);
Next(i, u){
int v = e[i].v; if(v == fa) continue;
dfs2(v, u), Merge(rt[u], rt[v], 1, n);
if(s[v] == mx[u]) tmp += query(rt[v], 1, n, max(1, 2 * mx[u] - s[1]), min(s[1] - 2 * se[u], 2 * s[u] - s[1]));
else tmp += query(rt[v], 1, n, 1, min(s[1] - 2 * mx[u], 2 * s[u] - s[1]));
}
add(s[u], -1), update(rt[u], 1, n, s[u], s[u], 1);
tmp -= query(rt[u], 1, n, max(1, s[1] - 2 * s[u]), s[1] - 2 * mx[u]);
if(s[1] - 2 * mx[u] >= 1) tmp += ton[s[1] - 2 * mx[u]];
if(s[1] - 2 * s[u] >= 1) tmp -= ton[s[1] - 2 * s[u] - 1];
ans += 1ll * tmp * u;
}
int main(){
T = read();
while(T--){
memset(h, 0, sizeof(h)), memset(rt, 0, sizeof(rt));
memset(s, 0, sizeof(s)), memset(mx, 0, sizeof(mx));
memset(se, 0, sizeof(se)), memset(ton, 0, sizeof(ton));
rep(i, 1, tot) t[i].l = t[i].r = t[i].sum = 0;
tot = cnt = ans = 0;
n = read();
rep(i, 1, n - 1) u = read(), v = read(), Add(u, v);
dfs1(1, 0);
rep(i, 1, n) ++ton[s[i]];
rep(i, 1, n) ton[i] = ton[i - 1] + ton[i];
dfs2(1, 0);
printf("%lld\n", ans);
}
return 0;
}

CSP2019 Day2T3 树的重心的更多相关文章

  1. CSP2019 树的重心 题解

    本题当然可以通过大力讨论每棵子树的size的大小关系,然后用各种数据结构暴力维护.但是我更倾向于用一种更为性质的做法. 首先讲一下我在考场上想到的做法(没写).就是考虑换根,在换根的过程中计算每一条边 ...

  2. POJ3107Godfather[树形DP 树的重心]

    Godfather Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 6121   Accepted: 2164 Descrip ...

  3. poj1655 树的重心 树形dp

    树的重心定义为:找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡. 处理处每个节点的孩子有几个,和树的大小就好了. #include< ...

  4. poj3107 求树的重心(&& poj1655 同样求树的重心)

    题目链接:http://poj.org/problem?id=3107 求树的重心,所谓树的重心就是:在无根树转换为有根树的过程中,去掉根节点之后,剩下的树的最大结点最小,该点即为重心. 剩下的数的 ...

  5. 树形DP求树的重心 --SGU 134

    令一个点的属性值为:去除这个点以及与这个点相连的所有边后得到的连通分量的节点数的最大值. 则树的重心定义为:一个点,这个点的属性值在所有点中是最小的. SGU 134 即要找出所有的重心,并且找出重心 ...

  6. 求树的重心(POJ1655)

    题意:给出一颗n(n<=2000)个结点的树,删除其中的一个结点,会形成一棵树,或者多棵树,定义删除任意一个结点的平衡度为最大的那棵树的结点个数,问删除哪个结点后,可以让平衡度最小,即求树的重心 ...

  7. codeforces 685B Kay and Snowflake 树的重心

    分析:就是找到以每个节点为根节点的树的重心 树的重心可以看这三篇文章: 1:http://wenku.baidu.com/link?url=yc-3QD55hbCaRYEGsF2fPpXYg-iO63 ...

  8. POJ 1655 Balancing Act (求树的重心)

    求树的重心,直接当模板吧.先看POJ题目就知道重心什么意思了... 重心:删除该节点后最大连通块的节点数目最小 #include<cstdio> #include<cstring&g ...

  9. POJ3107--Godfather(树的重心)

    vector建图被卡了..改为链式前向星500ms过的..差了四倍多?... 表示不太会用链表建图啊..自己试着写的,没看模板..嗯..果然错了..落了一句话orz 树的重心就是找到一个树中一个点,其 ...

随机推荐

  1. Codeforces 855B:Marvolo Gaunt's Ring(枚举,前后缀)

    B. Marvolo Gaunt's Ring Professor Dumbledore is helping Harry destroy the Horcruxes. He went to Gaun ...

  2. Stirling's Formula

    目录 Stirling's Formula Keith Conrad. Stirling's Formula. Stirling's Formula \[\lim_{n \rightarrow \in ...

  3. Certified Robustness to Adversarial Examples with Differential Privacy

    目录 概 主要内容 Differential Privacy insensitivity Lemma1 Proposition1 如何令网络为-DP in practice Lecuyer M, At ...

  4. 开一场自己的特斯拉灯光秀「GitHub 热点速览 v.22.01」

    作者:HelloGitHub-小鱼干 新的一年开始了,本周特推是两款有意思的游戏,一个是末日生存游戏,让你有"危机感"地过好新的一年,而另外一款则是编程游戏,有代码就有一切的一款游 ...

  5. docker启动emqx官方镜像,顺便启动exporter

    注意,我是把把官方镜像放到了自己的仓库 1.emqx官方镜像启动 docker run -d --name emqx31 -p 1883:1883 -p 8083:8083 -p 8883:8883 ...

  6. Kafka基础教程(三):C#使用Kafka消息队列

    接上篇Kafka的安装,我安装的Kafka集群地址:192.168.209.133:9092,192.168.209.134:9092,192.168.209.135:9092,所以这里直接使用这个集 ...

  7. 论文翻译:2021_Semi-Blind Source Separation for Nonlinear Acoustic Echo Cancellation

    论文地址:https://ieeexplore.ieee.org/abstract/document/9357975/ 基于半盲源分离的非线性回声消除 摘要: 当使用非线性自适应滤波器时,数值模型与实 ...

  8. 在CentOS 7.6 以 kubeadm 安装 Kubernetes 1.15 最佳实践

    前言 Kubernetes作为容器编排工具,简化容器管理,提升工作效率而颇受青睐.很多新手部署Kubernetes由于"scientifically上网"问题举步维艰,本文以实战经 ...

  9. spring controller获取web前端post数据乱码解决

    web.xml文件加上如下代码<!-- post请求乱码拦截器 --><filter> <filter-name>CharacterEncodingFilter&l ...

  10. vue备用

    handleCheck (item) { if (item.isChecked) { this.checkData.push(item.id) this.checkData = _.uniqWith( ...