CSP2019·洛谷·树的重心

初见安~这里是传送门:洛谷P5666 树的重心

题解

当时做这个题本来写了AB性质的部分分,结果链炸了……氦。

题意就是枚举断边,分别求两个子树的重心的编号和。可以顺着题意来,先考虑如何快速求一个树的重心。

因为重心满足其子树大小都不超过size/2,所以一棵树的重心一定在从根节点开始的一条重链上。沿着重链跳就一定能找到一个合法的重心。

考虑优化,可以倍增跳。维护p[i][j]表示i往下跳2^j步后的点是哪个。看当前点往下的子树的大小,如果大于size/2,那还要往下。

找到重心后,因为一棵树可能有两个重心,所以检查一下该点的前后两个点是否有可能作为重心即可。

 

接下来看怎么维护这个东西。形如换根dp。树上的fa父子关系保持不变。

假设当前边是u->v,u是v的父亲,断开这条边后u失去了v这个子树,即u的子树是整棵树去掉v子树。可以用这个维护当前size。

接下来是维护重链。如果v是u的重儿子,断边了那u就只能连向次重儿子,所以一开始就要预处理重儿子和次重儿子。u的重儿子还可能是从fa[u]过来,判一下size的关系即可。

然后是倍增数组。其实从u出发沿可能已经改变了的重儿子跳一遍就可以了,这是更新。

上述操作v都不需要改变,因为它的子树一直是它下面的子树,没有变化。

下面就是重点了——怎么跳重心。

分别从u和v出发如第一段说的倍增跳就行了。(什)判定方式一直是看子树大小与size的关系。

操作完成后dfs v。要先保留当前的size,son之类的信息,因为下一层后u就是v的子树了。再结合前文,其实你还需要用到fa信息,所以父节点也要跟着走

遍历完子树过后回溯离开u,要还原所有前面更改过的size,son和fa,包括倍增数组。所以建议都copy出去一个数组保留原始信息。

差不多是这样。本题重点就在于倍增跳重心和换根。上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 300005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

struct edge{int to, nxt;} e[maxn << 1];
int head[maxn], k = 0;
void add(int u, int v) {e[k] = {v, head[u]}; head[u] = k++;}

int T, n, size[maxn], size2[maxn];
int fa[maxn], son[maxn], son2[maxn], fa_tmp[maxn], son_tmp[maxn], p[maxn][20];
void dfs1(int u) {
	size[u] = 1;
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(v == fa[u]) continue;
		fa[v] = u; dfs1(v); size[u] += size[v];//浅显易懂的预处理 son2是次重儿子
		if(size[v] > size[son[u]]) son2[u] = son[u], son[u] = v;
		else if(size[v] > size[son2[u]]) son2[u] = v;
	}
	p[u][0] = son[u];//p是倍增数组
	for(int i = 1; i <= 18; i++) p[u][i] = p[p[u][i - 1]][i - 1];
} 

ll ans = 0;//↓check当前点是不是可以作为重心,可以的话返回贡献。
ll check(int u, int sum) {return u * (max(size2[son_tmp[u]], sum - size2[u]) <= (sum >> 1));}
void dfs2(int u) {
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(v == fa[u]) continue;
		size2[u] = size[1] - size[v];//size2就是换根时的size
		if(son[u] == v) son_tmp[u] = son2[u]; else son_tmp[u] = son[u];//看是否要换重链
		if(size2[son_tmp[u]] < size2[fa[u]]) son_tmp[u] = fa[u];//是否可以从fa过来
		
		p[u][0] = son_tmp[u];//更新倍增数组
		for(int j = 1; j <= 18; j++) p[u][j] = p[p[u][j - 1]][j - 1];
		fa_tmp[u] = 0, fa_tmp[v] = 0;//断边
		
		register int point = u;//跳u子树的重心
		for(int j = 18; j >= 0; j--) if((size2[u] - size2[p[point][j]]) <= (size2[u] >> 1)) point = p[point][j];
		ans += check(point, size2[u]) + check(fa_tmp[point], size2[u]) + check(son_tmp[point], size2[u]);//检查前后三个点
		point = v;//跳v子树的重心
		for(int j = 18; j >= 0; j--) if((size2[v] - size2[p[point][j]]) <= (size2[v] >> 1)) point = p[point][j];
		ans += check(point, size2[v]) + check(fa_tmp[point], size2[v]) + check(son_tmp[point], size2[v]);
		fa_tmp[u] = v;//接回去
		dfs2(v);
	}
	size2[u] = size[u];//这三行都是在还原
	son_tmp[u] = son[u]; fa_tmp[u] = fa[u]; p[u][0] = son[u];
	for(int i = 1; i <= 18; i++) p[u][i] = p[p[u][i - 1]][i - 1];
}

signed main() {
	T = read();
	while(T--) {
		n = read(); memset(head, -1, sizeof head); k = 0; ans = 0;//清空
		memset(p, 0, sizeof p); memset(son, 0, sizeof size); memset(son2, 0, sizeof size2);
		for(int i = 1, u, v; i < n; i++) u = read(), v = read(), add(u, v), add(v, u);
		dfs1(1);//预处理基本信息
		memcpy(size2, size, sizeof size2); memcpy(fa_tmp, fa, sizeof fa_tmp); memcpy(son_tmp, son, sizeof son_tmp);//先赋过去
		dfs2(1);//换根
		printf("%lld\n", ans);
	}
	return 0;
}

挺毒瘤的一个题……

迎评:)
——End——

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值