【CSP-S2019】D2T3 树的重心

CSP-S2019 D2T3 树的重心

题目

题目描述

小简单正在学习离散数学,今天的内容是图论基础,在课上他做了如下两条笔记:

  1. 一个大小为 nn 的树由 nn 个结点与 n1n − 1 条无向边构成,且满足任意两个结点间有且仅有一条简单路径。在树中删去一个结点及与它关联的边,树将分裂为若干个子树;而在树中删去一条边(保留关联结点,下同),树将分裂为恰好两个子树。
  2. 对于一个大小为 nn 的树与任意一个树中结点 cc,称 cc 是该树的重心当且仅当在树中删去 cc 及与它关联的边后,分裂出的所有子树的大小均不超过 n2\lfloor \frac{n}{2} \rfloor(其中 x\lfloor x \rfloor 是下取整函数)。对于包含至少一个结点的树,它的重心只可能有 1122 个。

课后老师给出了一个大小为 nn 的树 SS,树中结点从 1n1 \sim n 编号。小简单的课后作业是求出 SS 单独删去每条边后,分裂出的两个子树的重心编号和之和。即:

(u,v)E(1xnxSux+1ynySvy) \sum_{(u,v) \in E} \left( \sum_{1 \leq x \leq n \atop 且 x 号点是 S'_u 的重心} x + \sum_{1 \leq y \leq n \atop 且 y 号点是 S'_v 的重心} y \right)

上式中,EE 表示树 SS 的边集,(u,v)(u,v) 表示一条连接 uu 号点和 vv 号点的边。SuS'_u​ 与 SvS'_v​ 分别表示树 SS 删去边 (u,v)(u,v) 后,uu 号点与 vv 号点所在的被分裂出的子树。

小简单觉得作业并不简单,只好向你求助,请你教教他。

分析

40 分的做法是枚举一条边,将它断掉后再暴力求出两边的重心。

结论: 重心必须在以根节点为起点的重链上。

证明: 不会。。。

然后我们可以利用这个结论,枚举每一个点,统计每个点在删掉边后对答案的贡献次数即可。

当我们分裂出一棵完整的子树时,就像这样:

对于以 vv 为根的子树的这部分,根据结论,我们沿着以 vv 为起点的重链往下跳就可以了。

对于剩余的部分,我们考虑换根。将剩余的部分提成以 uu 为根的树,然后暴力修改重儿子和重链即可。

我们可以用倍增来优化寻找重心的这一部分。

参考代码

#include <cstdio>
#include <algorithm>
using namespace std;

const int Maxn = 300000;
const int Maxlog = 19;
typedef long long ll;

#ifdef LOACL
bool ___;
#endif

struct Edge {
	int to;
	Edge *nxt;
};
Edge pool[Maxn * 2 + 5];
Edge *G[Maxn + 5], *ecnt;
inline void addedge(int u, int v) {
	Edge *p = ++ecnt;
	p->to = v, p->nxt = G[u];
	G[u] = p;
}

int N;
int siz[Maxn + 5];
int mxson[Maxn + 5], mx2son[Maxn + 5];
ll ans;

int s[Maxn + 1][Maxlog + 2];
int f[Maxn + 5];

inline void clear() {
	for(int i = 1; i <= N; i++)
		mxson[i] = mx2son[i] = 0;
	for(int i = 1; i <= N; i++)
		for(int j = 0; j <= Maxlog; j++)
			s[i][j] = 0;
	for(int i = 1; i <= N; i++)
		G[i] = NULL;
	ecnt = &pool[0];
	ans = 0;
}
inline void modify(int u) {
	for(int i = 1; i <= Maxlog; i++)
		s[u][i] = s[s[u][i - 1]][i - 1];
}
void PreDFS(int u, int fa) {
	siz[u] = 1, f[u] = fa;
	for(Edge *p = G[u]; p != NULL; p = p->nxt) {
		int v = p->to;
		if(v == fa) continue;
		PreDFS(v, u);
		siz[u] += siz[v];
		if(siz[v] > siz[mxson[u]]) {
			mx2son[u] = mxson[u];
			mxson[u] = v;
		} else if(siz[v] > siz[mx2son[u]])
			mx2son[u] = v;
	}
	s[u][0] = mxson[u];
	modify(u);
}
inline void calc(int u) {
	int tot = siz[u], rt = u;
	for(int i = Maxlog; i >= 0; i--)
		if(tot - siz[s[u][i]] <= tot / 2)
			u = s[u][i];
	if(siz[s[u][0]] <= tot / 2) ans += u;
	if(u != rt && siz[u] <= tot / 2) ans += f[u];
}
void DFS(int u, int fa) {
	if(fa != 0) calc(fa), calc(u);
	int flag = 0, t1;
	if(N - siz[u] > siz[mxson[u]]) {
		t1 = mx2son[u], flag = 1;
		mx2son[u] = mxson[u], mxson[u] = fa;
	} else if(N - siz[u] > siz[mx2son[u]]){
		t1 = mx2son[u], flag = 2;
		mx2son[u] = fa;
	}
	for(Edge *p = G[u]; p != NULL; p = p->nxt) {
		int v = p->to;
		if(v == fa) continue;
		siz[u] += siz[fa] - siz[v], f[u] = v;
		if(v == mxson[u]) s[u][0] = mx2son[u];
		else s[u][0] = mxson[u];
		modify(u);
		DFS(v, u);
		siz[u] -= siz[fa] - siz[v], f[u] = fa;
	}
	if(flag == 1) {
		mxson[u] = mx2son[u], mx2son[u] = t1;
	} else if(flag == 2) mx2son[u] = t1;
	s[u][0] = mxson[u];
	modify(u);
}

#ifdef LOACL
bool ____;
#endif

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	int _;
	scanf("%d", &_);
	while(_--) {
		scanf("%d", &N);
		clear();
		for(int i = 1; i < N; i++) {
			int u, v;
			scanf("%d %d", &u, &v);
			addedge(u, v), addedge(v, u);
		}
		PreDFS(1, 0);
		DFS(1, 0);
		printf("%lld\n", ans);
	}
#ifdef LOACL
	printf("%.2f\n", (&____ - &___) / 1048576.0);
#endif
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章