题解: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;
}

浙公网安备 33010602011771号