2019 徐州 M. Kill the tree(树的重心的性质,子树合并 + 徐州赛区感想)

在这里插入图片描述
题目大意:转译过来就是 给一棵 n 个点有根树,按升序输出所有点为根节点的子树的所有重心。

徐州现场因为这题体验直接拉满,前4个小时签到打崩了直接拉队友陪葬(三个人看了4个小时A和F,全是我的锅),封榜后和队友开这题,第一眼完全没思路,但是有一种知识量全在掌握范围之内有机会做出来的直觉。怀着这种直觉我决定最后一个小时要用上我全部的知识来绝杀这题。由于之前在jxccpc做过式子定义相同的题已经知道就是要求重心,队友直接为我搬出了树的重心的所有可能用到的性质(期间多次想起点分治,因为分治过程就是一个找重心的过程,不过这个过程会破坏树形,每次放下又因为没有思路而回想起来),正在对这些性质逐条斟酌的时候(期间提过一次希望能用类似合并两棵树的方法来搞),队友突然补充了一句,如果两棵树合并新的重心会在这两个在重心的链上,结合一下自己的经验想到了做法。

(此时我已确定这个做法可行,已经开始想要如何实现了,嘴里却一直在问队友觉得这个方法如何,希望队友能得到肯定的答案而和我产生共鸣加速思考实现的过程,但队友因为不太熟悉子树合并的过程遂放弃)

然后就是我们队2019年赛季最惨场面了,在剩余大概18分钟的时候已经按照大概的思路码完。因为代码出乎意料的好写,又一次激起了我全部的希望,希望能在接下来18分钟内调试完。回想起来徐州那一场状态非常奇怪,先是两道签到题代码频繁出错,漏写else 导致队友白看2个小时没有开到E题,另一名队友则和我打表打到比赛结束,这题写完发现逻辑判断好多写反了。最后的关头发现这个错误时开始紧张了起来,预感可能会调不出来死在某些小方面。开始了长达15分钟的逻辑check。

最后的最后,交了三发都wa了,此时只剩不到5分钟(大概2-3分钟),脑子一片混乱,队友多次提到某个地方少了一个else 会不会出现不更新的情况(又少了一个else),被我忽略,最终没过此题。
在这里插入图片描述
(就是此处的else)
准备颁奖时突然脑子里突然闪过这个地方(回光返照?),猛地明白了队友的多次疑问,心态开始瓦解,赛后用这个方法尝试群友给的原题,果然能过。

这一场锅直接拉满,幸好队友心态都不错没有责怪我,在火车上整理了一下这场因为我的问题错失了稳稳的银牌(南昌也错失了好可惜)。

此前比赛会因为太在乎自己的表现力而紧张,现场降智严重(易受外界干扰),在2019年多校和网络赛以及第一次CCPC铁首和厦门之挫,终于能坦白接受自己的失败。(结果似乎因为越来越随意而打崩了所有的比赛)我的2019年所有ICPC,CCPC参赛全部结束,没能取得理想的成绩。


回归正题:由于两棵子树合并,他们的重心一定在两个重心的连线上,一棵树最多两棵重心,且一定挨着(这题几乎用上了所有的重心的性质,从这个做法出发是一道不错的题)。

一个做法是,采用树形dp的思路,通过不断合并子树来合并两个重心。这里要解决两个问题:
1.如何合并两个重心
2.题目问的是所有重心(一棵树最多两个),但是每次合并两棵重心代码必然繁琐无比

对于第一个问题:既然新的重心一定在两棵子树到当前根节点的链上,那么新的重心的位置只有两种可能,要么在根节点上,要么在两端的链上。可以通过不断将重心往根节点方向爬,深度较大的点一定是重心。

什么条件下重心可以往上爬: 设当前重心为u,根节点为rt,sz[u] 是 u 结点的为根节点的子树的结点个数。若u往上爬,u的所有子节点到u的距离会加一,其它点到u的距离会减一,根据题目给的计算式,这个该变量必须是负值才可以往上爬:那么得到一个条件是:sz[rt] - sz[u] > sz[u],满足这个条件 u 就可以向上爬,直到 u == rt 或者 条件不满足

两棵重心可以通过这个方法先寻找到深度较大的那个,另外一个必然是它的父结点(或者找到深度较小的那个,另外一个必然是它的子节点),找完所有点的重心后再处理一遍即可:


赛后重写的代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
vector<int> g[maxn],h[maxn];
int n,f[maxn],p[maxn],son[maxn],deep[maxn];
int 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];
	}
	//printf("%d %d %d\n",rt,x,y);
	if(deep[x] > deep[y]) son[rt] = x;
	else son[rt] = y;
}
void dfs(int u,int fa) {
	f[u] = 1,son[u] = u;
	p[u] = fa;
	for(auto it : g[u]) {
		if(it == fa) continue;
		deep[it] = deep[u] + 1;
		dfs(it,u);
		f[u] += f[it];
		upd(u,son[u],son[it]);
	}
	
}
int main() {
	scanf("%d",&n);
	for(int i = 1; i < n; i++) {
		int u,v;scanf("%d%d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	deep[1] = 1;
	dfs(1,0);
	for(int i = 1; i <= n; i++) {
		h[i].push_back(son[i]);
		if(p[son[i]] && f[i] - f[son[i]] == f[son[i]]) 
			h[i].push_back(p[son[i]]);
		sort(h[i].begin(),h[i].end(),less<int>());
		for(int j = 0; j < h[i].size(); j++) {
			printf("%d%s",h[i][j],j == h[i].size() - 1 ? "\n" : " ");
		} 
	}
	return 0;
}
  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值