首先考虑二分,然后发现最小长度越大的话,赛道就越少。所以可以用最终的赛道个数来判断长度是否合理。问题转化为给定一个长度,问最多有多少条互不重叠路径比这个给定长度大。
考虑贪心,毕竟贪心也是二分check函数的常用做法。原图毕竟为一棵树,每条路径都由一个端点一个终点和他们的\(LCA\)之间的连边组成。我们直接枚举lca,然后枚举lca的子链且该链上无被找到的链,对于没有找到的链,都匹配上子链上最小的、浪费最少的,考虑这样做为什么是对的,因为如果不练子链上,而去连父亲的话,最底下的链肯定就浪费了。所以可以贪心直接用递归子树,然后找到每个子树中的选择的链,不选择的链。就行了,匹配操作可以用二分优化。
#include <bits/stdc++.h> #define N 1000101 using namespace std; struct edg { int to, nex, len; }e[N]; int n, m, cnt, root = 1, ha, lin[N], dp[N], temp[N], vis[N], tot;//dp[i]表示i的点权 inline void add(int f, int t, int l) { e[++cnt].len = l; e[cnt].to = t; e[cnt].nex = lin[f]; lin[f] = cnt; } void dfs(int now, int fa, int len) { int tot = 0; for (int i = lin[now]; i; i = e[i].nex) { int to = e[i].to; if (to != fa) dfs(to, now, len); } for (int i = lin[now]; i; i = e[i].nex) { int to = e[i].to; if (to != fa) temp[++tot] = dp[to] + e[i].len; } sort(temp + 1, temp + 1 + tot); //将当前i到结尾的所有链处理出来。 // oprintf("%d %d\n", now, temp[tot]); for (int i = tot; i >= 1 && temp[i] >= len; i--) tot--, ha--; for (int i = 1; i <= tot; i++) { if (vis[i] == now) continue;//vis判断是否链经过now int a = len - temp[i];//开始二分 int l = i + 1, r = tot, mid, ans = tot + 1; while (l <= r) { mid = (l + r) >> 1; if (temp[mid] >= a)//如果当前的另一条链比a大的话,说明此时可以用来求解 { ans = mid; r = mid - 1; } else l = mid + 1; } while (vis[ans] == now && ans <= tot) ans++; if (ans <= tot) { ha--, vis[i] = vis[ans] = now; } } dp[now] = 0; for (int i = tot; i >= 1; i--)//找到第一个最长的链,然后 if (vis[i] != now) { dp[now] = temp[i]; break; } // printf("%d\n", dp[now]); } bool check(int mid)//判断mid,对于每条路径取到就停止,最后判断是否超过m个比mid大的路径 { ha = m; // now是当前能满足多少个路径。 memset(vis, 0, sizeof(vis)); dfs(root, 0, mid); return ha <= 0; } int main() { scanf("%d%d", &n, &m); int l = 0, r = 0; for (int i = 1; i < n; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); add(b, a, c); r += c; } int mid, ans; while (l <= r) { mid = (l + r) >> 1; if (check(mid)) { ans = mid; l = mid + 1; } else r = mid - 1; } printf("%d", ans); return 0; }