题解:P17320 [ICPC 2018 Nanjing R] Cherry and Chocolate

思路很顺,感觉比 L 简单很多。

将两个粉色节点在树上删除,设棕色节点所在连通块大小为 \(s\),则 Cherry 得到 \(n-s\) 分。

先固定第一个粉色节点 \(x\) 并在树上删除,再固定棕色节点 \(y\)。设 \(y\) 所在连通块的大小为 \(S\),若 \(S\geq 2\),则 Cherry 必然会在 \(y\) 所在的连通块选择一个点作为第二个粉色节点 \(z\),使得删除 \(z\)\(y\) 所在连通块大小 \(s\) 尽可能小。不难发现 \(z\) 一定是 \(y\) 的邻点,于是固定 \(x,y\) 后,\(s=S-\operatorname{mxp}(y)\),其中 \(\operatorname{mxp}(y)\) 表示删除 \(y\) 后最大的连通块大小。

再来考虑 \(y\) 的选择,Chocolate 要让 \(s\) 尽可能大,如果固定 \(y\) 所在的连通块,那么 Chocolate 必然会选择 \(\operatorname{mxp}(y)\) 最小的点。这不是我们重心吗?

问题转化成,对于树上的每条有向边 \(u\to v\),求删去这条边后 \(v\) 所在连通块的重心和对应的 \(\operatorname{mxp}\)

设这个连通块大小为 \(C\)。考虑将这个连通块以 \(v\) 为根向下递归找重心。如果 \(v\) 的所有儿子子树大小都 \(\leq\dfrac{C}{2}\),那么 \(v\) 就是重心;否则重心必然在唯一一个大小 \(>\dfrac{C}{2}\) 的子树内。

对于树上的每条有向边 \(e=u\to v\),预处理 \(nxt_e\) 表示 \(v\) 去掉 \(v\to u\) 这条边后,剩下的边中对应子树大小最大的边。那么我们只需要对 \(nxt\) 倍增即可 \(\mathcal{O}(\log{n})\) 查询重心。而预处理 \(nxt\) 只需要对每个点求出最大和次大的子树大小即可。

时间复杂度为 \(\mathcal{O}(n\log{n})\)

代码很好写。

主要代码
#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using i128 = __int128;
using ui = unsigned int;
using ull = unsigned long long;
using u128 = unsigned __int128;
using ld = long double;
using pii = pair<int, int>;
const int MAXN = 1e5 + 5, LOGN = 18;

template<typename T> T lowbit(T x) { return x & -x; }
template<typename T> void chkMin(T &x, T y) { x = y < x ? y : x; }
template<typename T> void chkMax(T &x, T y) { x = x < y ? y : x; }
constexpr int lg2(ll x) { return 63 ^ __builtin_clzll(x); }
constexpr ll bitCeil(ll x) { return x == 1 ? 1ll : 1ll << lg2(x - 1) + 1; }

int n, fa[MAXN], sz[MAXN], sze[MAXN << 1], c[MAXN << 1];
int mx[MAXN], smx[MAXN];
int nxt[LOGN][MAXN << 1];
pii edges[MAXN << 1];
vector<pii> T[MAXN];

void dfs(int u) {
	sz[u] = 1;
	for (auto [v, id] : T[u]){
		if (v == fa[u]) continue;
		fa[v] = u;
		dfs(v);
		sz[u] += sz[v];
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n;
	for (int i = 1; i < n; ++i) {
		int u, v;
		cin >> u >> v;
		T[u].emplace_back(v, (i - 1) * 2);
		T[v].emplace_back(u, (i - 1) * 2 + 1);
		edges[(i - 1) * 2] = {u, v};
		edges[(i - 1) * 2 + 1] = {v, u};
	}

	dfs(1);
	int m = n - 1 << 1;
	for (int i = 0; i < m; ++i) {
		auto [u, v] = edges[i];
		sze[i] = fa[v] == u ? sz[v] : n - sz[u];
	}

	for (int u = 1; u <= n; ++u) {
		mx[u] = smx[u] = -1;
		for (auto [v, id] : T[u]) {
			if (mx[u] == -1 || sze[id] > sze[mx[u]]) {
				smx[u] = mx[u];
				mx[u] = id;
			} else if (smx[u] == -1 || sze[id] > sze[smx[u]]) {
				smx[u] = id;
			}
		}
	}

	fill(nxt[0], nxt[0] + m, -1);
	for (int i = 0; i < m; ++i) {
		int u = edges[i].second;
		nxt[0][i] = mx[u] != (i ^ 1) ? mx[u] : smx[u];
	}

	int logn = lg2(n);
	for (int i = 1; i < logn; ++i) {
		for (int j = 0; j < m; ++j) {
			nxt[i][j] = nxt[i - 1][j] == -1 ? -1 : nxt[i - 1][nxt[i - 1][j]];
		}
	}

	for (int i = 0; i < m; ++i) {
		int e = i;
		for (int k = logn - 1; k >= 0; --k) {
			int ne = nxt[k][e];
			if (ne != -1 && sze[ne] * 2 > sze[i]) e = ne;
		}
		c[i] = sze[i] - max(sze[i] - sze[e], nxt[0][e] != -1 ? sze[nxt[0][e]] : 0);
	}

	int ans = n;
	for (int u = 1; u <= n; ++u) {
		int val = 0;
		for (auto [v, id] : T[u]) chkMax(val, c[id]);
		chkMin(ans, val);
	}
	cout << n - ans;
	return 0;
}
posted @ 2026-08-29 12:03  P2441M  阅读(9)  评论(0)    收藏  举报