题解:P17834 [CSP-S 2026] 研发基地 / hub
深刻。
考虑 \(k=1\) 的时候取直径中点最优,进一步不难发现连通块必然包含直径中点。
考虑二分 \(mid\),求出令 \(\max d(i)\leq mid\) 的最少点数。枚举断边,对两个连通块分别求出在内部选一个连通块覆盖所有点的最少点数。对于一个连通块,以直径中点为根,对于每个深度为 \(d>mid\) 的点,其深度为 \(d-mid\) 的祖先必须被选中,自底向上求出必须被选中的点数即可。时间复杂度为 \(\mathcal{O}(n^2\log{n})\),考场上的思考就到此为止了。
考虑优化 check 的复杂度。每个连通块都求出以直径中点为根的信息看上去不太好做,考虑抛开这个结构直接去 DP。比较容易求出的是:\(f_u\) 表示强制选择点 \(u\) 覆盖 \(sub_u\) 的最少点数。令 \(h_u\) 表示子树高度,那么容易得到转移:
再来考虑如果不强制选择点 \(u\) 怎么做。观察到如果有至少两个儿子 \(v\) 满足 \(h_v\geq mid\),那么 \(u\) 是必选的,因此如果 \(u\) 可以不被选择,它至多只有一个儿子满足 \(h_v\geq mid\)。由此不难看出,不强制选择点 \(u\) 的方案,形如从 \(f_u\) 给出的方案中删去一条从 \(u\) 开始的链。
设计 \(g_u\) 表示这条链的最大长度。若 \(u\) 恰好有一个儿子 \(v\) 满足 \(h_v\geq mid\),则设 \(d=\max\limits_{w\in son_u\land w\neq v}\{h_w+1\}\),可以得到转移:
否则 \(g_u=0\)。
这个状态就很方便换根了。对于每个 \(u\),我们求出 \(u\) 子树内/子树外的最大、次大的 \(h\) 和最大的 \(h\) 对应的节点,转移 \(g\) 的时候直接判断是否有 \(mx>mid\land smx\leq mid\) 就好了。
时间复杂度 \(\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 = 1e6 + 5;
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 tid, tc, n, k, fa[MAXN];
int deg[MAXN], st[MAXN], ed[MAXN], T[MAXN << 1];
int f[MAXN], g[MAXN], g2[MAXN];
pii edges[MAXN];
vector<int> ord;
struct Info {
int mx, mxp, smx;
} a[MAXN], b[MAXN];
bool check(int mid) {
fill(f + 1, f + n + 1, 1);
for (int u : ord | views::reverse) {
g[u] = a[u].mx > mid && a[u].smx <= mid ? min(g[a[u].mxp] + 1, mid - a[u].smx) : 0;
if (fa[u] && a[u].mx >= mid) f[fa[u]] += f[u];
}
if (f[1] - g[1] <= k) return true;
for (int u : ord) {
for (int _ = st[u]; _ < ed[u]; ++_) {
int v = T[_];
if (v == fa[u]) continue;
int nf = f[u] - (a[v].mx >= mid ? f[v] : 0);
g2[v] = b[v].mx > mid && b[v].smx <= mid ? min(b[v].mxp == fa[u] ? g2[u] + 1 : g[b[v].mxp] + 1, mid - b[v].smx) : 0;
if (nf - g2[v] + f[v] - g[v] <= k) return true;
if (b[v].mx >= mid) f[v] += nf;
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> tid >> tc;
while (tc--) {
cin >> n >> k;
fill(deg + 1, deg + n + 1, 0);
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
edges[i] = {u, v};
++deg[u];
++deg[v];
}
st[1] = ed[1] = 0;
for (int i = 1; i < n; ++i) st[i + 1] = ed[i + 1] = st[i] + deg[i];
for (int i = 1; i < n; ++i) {
auto [u, v] = edges[i];
T[ed[u]++] = v;
T[ed[v]++] = u;
}
ord.clear();
ord.emplace_back(1);
for (int i = 0; i < n; ++i) {
int u = ord[i];
for (int _ = st[u]; _ < ed[u]; ++_) {
int v = T[_];
if (v == fa[u]) continue;
fa[v] = u;
ord.emplace_back(v);
}
}
int D = 0;
fill(a + 1, a + n + 1, Info{0, 0, 0});
for (int u : ord | views::reverse) {
chkMax(D, a[u].mx + a[u].smx);
if (u == 1) break;
int p = fa[u];
if (a[u].mx + 1 >= a[p].mx) {
a[p].smx = a[p].mx;
a[p].mx = a[u].mx + 1;
a[p].mxp = u;
} else {
chkMax(a[p].smx, a[u].mx + 1);
}
}
for (int u : ord) {
array<pii, 3> h;
for (int _ = st[u]; _ < ed[u]; ++_) {
int v = T[_];
pii val = {v == fa[u] ? b[u].mx + 1 : a[v].mx + 1, v};
for (auto &c : h) {
if (val > c) swap(val, c);
}
}
for (int _ = st[u]; _ < ed[u]; ++_) {
int v = T[_];
if (v == fa[u]) continue;
pii x = h[0], y = h[1];
if (x.second == v) {
x = h[1];
y = h[2];
} else if (y.second == v) {
y = h[2];
}
b[v] = {x.first, x.second, y.first};
}
}
int l = 0, r = D + 1 >> 1;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l << '\n';
}
return 0;
}

浙公网安备 33010602011771号