Kill the tree(树的重心,合并子树)

Link
大概题意:给一个 n n n个点的树, 求每个子树的重心, 如果子树的重心不止一个, 就按照升序以空格分割。

重心性质
1,以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
2,树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
3,把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
4,在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
5,树的重心最多只有两个, 且两个重心相邻。

思路

参考题解:
基于性质3, 显然对于两颗子树的合并, 其产生新的重心必然在之前两棵树的重心之间的路径上,显然只需让两个重心不断上移即可找到重心所在。上移过程需要显然子树中每个点到以这个点为根的子树中的每一个点的距离和。
利用以树的重心为根时,所有子树的大小都不超过整棵树大小的一半来考虑,什么条件下重心可以往上爬: 设当前重心为u,根节点为rt,sz[u] 是 u 结点的为根节点的子树的结点个数。若u往上爬,u的所有子节点到u的距离会加一,其它点到u的距离会减一,根据题目给的计算式,这个该变量必须是负值才可以往上爬:那么得到一个条件是:sz[rt] - sz[u] > sz[u],满足这个条件 u 就可以向上爬,直到 u == rt 或者 条件不满足

可以考虑设计递归, 从下到上不断合并子树, 求出每个子树的重心。

代码中为什么要选择深度较大的当重心:这是因为深度较大则说明则至少它是在还有位置上升时没上升,即满足 f [ r t ] − f [ x ] ≤ f [ x ] f[rt] - f[x] \leq f[x] f[rt]f[x]f[x]才停止上升的, 而深度较高的显然无法确定

细节见于代码

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 10;

int n, f[N], deep[N], core[N], p[N]; // core树的重心

int h[N], e[N << 1], ne[N<<1], cnt;

void add(int u, int v)
{
	e[cnt] = v, ne[cnt] = h[u], h[u] = cnt ++;
	e[cnt] = u, ne[cnt] = h[v], h[v] = cnt ++;
}

void upd(int rt, int x, int y) // 合并子树,让两棵树的重心不断上移
{
	while(deep[x] > deep[rt] && f[rt] - f[x] > f[x])
		x = p[x];
	while(deep[y] > deep[rt] && f[rt] - f[y] > f[y])
		y = p[y];
	if(deep[x] > deep[y]) core[rt] = x;
	else core[rt] = y;
}

void dfs(int u, int fa)
{
	p[u] = fa;
	f[u] = 1;
	core[u] = u;
	for(int i = h[u]; ~i; i = ne[i])
	{
		int v = e[i];
		if(v == fa) continue;
		deep[v] = deep[u] + 1;
		dfs(v, u);
		f[u] += f[v];
		upd(u, core[u], core[v]);
	}
}
int main()
{
	memset(h, -1, sizeof h);
	scanf("%d", &n);
	for(int i = 1; i < n; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
	}
	deep[1] = 1;
	dfs(1, 0);
	for(int  i = 1; i <= n; i ++)
	{
		int u = core[i];
		int v = -1;
		if(i != core[i] && f[i] - f[core[i]] == f[core[i]]) // 如果有两个重心必然相邻
		{
			v = p[core[i]];
		}
		if(v == -1) printf("%d\n", u);
		else
		{
			if(u > v) swap(u, v);
			printf("%d %d\n", u, v);
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值