20260606 - 部分分训练 2 总结
\(20pts\):\(m = 1\)
因为只要选一条,所以直接选直径即可。
\(40pts\):\(b_i = a_i + 1\)
链的情况直接二分答案即可。
\(55pts\):\(a_i = 1\)
菊花的情况只有两种:
- 单独一条链
- 和另一条链合并
可以二分长度,搜索时可以用一个 \(multiset\) 来维护第一个比它大的,然后注意迭代器的问题。
\(80pts\):分支 \(\le 3\)
除根节点外,至多只有 \(2\) 个儿子,那么只有 \(2\) 种情况。
- 从儿子的最长路连上去
- 两个儿子连上
显然,如果两个儿子能连上,就一定会连,不然会挤占其他点的生存空间。
\(100pts\):没有特殊性质
我们发现,把儿子配对的最长路看作一个点,就是 \(a_i = 1\) 的做法了。
维护一个 \(multiset\),然后每次加入点,配对完了就删掉。
注意迭代器。
#include <bits/stdc++.h>
using namespace std;
#define debug(x) cout<<#x<<' '<<x<<'\n';
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 5e4 + 7;
constexpr int P = 998244353;
int n, m;
vector<PII> adj[N]; // 注意大小
int f[N], g[N];
void dfs(int u, int from, ll mid) {
g[u] = 0;
multiset<int> nums;
for (auto nxt : adj[u]) {
int v = nxt.first, w = nxt.second;
if (v == from) continue;
dfs(v, u, mid);
g[u] += g[v];
if (f[v] + w >= mid) {
++g[u];
} else {
nums.insert(f[v] + w);
}
}
for (auto it = nums.begin(); it != nums.end(); ) {
auto ir = nums.lower_bound(mid - *it);
if (ir == it) ++ir;
if (ir != nums.end()) {
nums.erase(ir);
it = nums.erase(it);
++g[u];
} else {
++it;
}
}
if (nums.empty()) f[u] = 0;
else f[u] = *prev(nums.end());
}
void solve() {
auto check = [&](ll mid) -> bool {
dfs(1, 0, mid);
return g[1] >= m;
};
ll L = 0, R = 1e9;
while (L < R) {
ll mid = (L + R + 1) / 2;
if (check(mid)) L = mid;
else R = mid - 1;
}
printf("%lld\n", L);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
adj[u].push_back({v, w});
adj[v].push_back({u, w});
if (u != 1) mark1 = false;
if (v != u + 1) mark2 = false;
}
solve();
return 0;
}

浙公网安备 33010602011771号