2026.06.06总结
部分分没想,直接去想正解了。
首先这个题中给你的图就是误导你,实际上最初的边一定是一棵树。
首先看到最小化最大,想到二分。
设当前二分到一个 \(mid\),存一个 \(tmp = m\)。
现在草稿纸上将树画出来,发现赛道就两种形式:
- 两个点为祖先后代关系。
- 两个点无关系,此时赛道一定经过它们的
lca
所以考虑 dfs 每个点,将其作为 lca 然后去看以这个点的子节点为端点的链,要么将某两条其拼起来,要么就是直接将这一条作为赛道。
对于单独将这一条链作为赛道的情况很好考虑,就是在其总长度 \(\ge mid\) 时,此时就将 \(tmp \gets tmp - 1\)
path[u].clear();
for (auto &[v, w] : g[u]) if (v != fa) {
dfs(v, u, mid);
path[u].pb(dp[v] + w);
}//path就是上面所说的以这个点的子节点为端点的链。
std::sort(all(path[u]));
while (!path[u].empty() && path[u].back() >= mid) path[u].pop_back(), tmp--;
然后是两条拼起来的情况,若当前枚举到一个 \(path_i\) 则我们肯定是找到最小的还没有选过的 \(path_j\) 使得 \(path_i + paht_j \ge mid\) 去与 \(path_i\) 配对,原因显然。
于是发现可以二分。
int L = i, R = len;
while (L + 1 < R) {
int Mid = L + R >> 1;
if (path[u][Mid] + path[u][i] >= mid) R = Mid;
else L = Mid;
}
为什么是从 \(i + 1\) 开始找呢?
因为 \(i\) 前面的已经枚举过了,如果有 \(i\) 前面有能和 \(i\) 配对的,则 \(i\) 已经被配对了。
如果当前二分到的 \(R\) 已经配对过了怎么办呢,只需往后找到第一个没被配对到的即可,原因显然。
while (vis[R] == u && R < len) R++;
if (R < len) tmp--, vis[R] = u, vis[i] = u;//将当前的 R 和 i 标记。
注意到这里标记都是设为 \(u\) 这是因为我之前一个写法每次清空 \(vis\) 会爆,于是就这样来标记,就不用每次清空了。
最后将最长的没用过的链传递上去即可。
dp[u] = 0;
for (int i = len - 1; i >= 0; i--) if (vis[i] != u) {
dp[u] = path[u][i];
break;
}
整体代码:
#include <bits/stdc++.h>
using ll = long long;
using ull = unsigned long long;
using i128 = __int128;
using db = double;
#define all(x) (x).begin(), (x).end()
#define pb push_back
#define pf push_front
#define mp std::make_pair
#define s second
#define f first
const int inf = 2e9;
const ll INF = 1LL << 60LL;
const int mod = 998244353;
const db eps = 1e-9;
std::mt19937_64 rng(std::chrono::system_clock::now().time_since_epoch().count());
const int N = 5e4 + 10;
int n, m, tmp, dp[N], vis[N];
std::vector<int> path[N];
std::vector<std::pair<int, int>> g[N];
void dfs(int u, int fa, int mid) {
path[u].clear();
for (auto &[v, w] : g[u]) if (v != fa) dfs(v, u, mid);
for (auto &[v, w] : g[u]) if (v != fa) {
path[u].pb(dp[v] + w);
}
std::sort(all(path[u]));
while (!path[u].empty() && path[u].back() >= mid) path[u].pop_back(), tmp--;
int len = path[u].size();
for (int i = 0; i < len; i++) if (vis[i] != u) {
int L = i, R = len;
while (L + 1 < R) {
int Mid = L + R >> 1;
if (path[u][Mid] + path[u][i] >= mid) R = Mid;
else L = Mid;
}
if (R == len) continue;
while (vis[R] == u && R < len) R++;
if (R < len) tmp--, vis[R] = u, vis[i] = u;
}
dp[u] = 0;
for (int i = len - 1; i >= 0; i--) if (vis[i] != u) {
dp[u] = path[u][i];
break;
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr), std::cout.tie(nullptr);
std::cin >> n >> m;
for (int i = 1; i < n; i++) {
int u, v, w;
std::cin >> u >> v >> w;
g[u].pb({v, w}), g[v].pb({u, w});
}
int l = 0, r = 4e8;
while (l + 1 < r) {
int mid = l + r >> 1;
tmp = m;
std::fill(vis, vis + n + 1, 0);
dfs(1, 0, mid);
if (tmp <= 0) l = mid;
else r = mid;
}
std::cout << l << '\n';
}
感觉有点吃思维,码量还好。

浙公网安备 33010602011771号