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';
}

感觉有点吃思维,码量还好。

posted @ 2026-06-07 15:15  yixinc  阅读(18)  评论(0)    收藏  举报