【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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章