题目
题目链接
题目大意:给出一棵 $n$ 个点的树,要求在树上修建 $m$ 条道路。每条边最多被一条道路覆盖。最大化赛道中长度最小的赛道。$1 \le n \le 5 \times 10^4, 1 \le m \le n - 1$。
思路
看到最大化最小值首先想到二分。
对于 $m = 1$ 的情况,只需要找到树的直径然后输出即可。
对于树是一条链的情况,二分道路长度判断即可。
对于菊花图,只需要在根结点遍历所有边,对边的长度排序后找出 $\max(w_1 + w_{n - 1}, w_2 + w_{n - 2}, \cdots)$ 即为答案。
然后就能拿到很可观的分数了。
其实菊花图的情况已经给了我们一些启示,对于每个结点向儿子的边里,一条道路最多覆盖其中的两条边。
考虑贪心。设当前结点为 $u$,儿子结点为 $v$,如果 $w_{u, v} \ge mid$,说明这条道路满足条件,直接加上即可。
如果 $w_{u, v} < mid$,有可能这条边能和这个结点到儿子的另外一条边组成一条道路,所以先加到一个集合里。
在集合里对每条边贪心地找到最小的边使得 $w_1 + w_2 \ge mid$,然后把这条边加进答案里。这些很显然是最优的。
如果集合中没有这种边满足两条边的和大于 $mid$,这条边也有可能跟父节点向下的边组成道路满足条件,而且最大化这条边一定是最优的,所以用一个变量来存储这种边的最大值。
这个过程可以用 multiset
实现。
不知道为什么我不开 O2 会 T 一两个点,大概是菊花图的情况这种算法时间比较慢,当然我自带大常数不是一天两天了。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <set>
template <class T> inline void read(T &x) { x = 0; int f = 0; char ch = getchar(); while (!isdigit(ch)) { f |= ch == '-'; ch = getchar(); } while (isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); } x = f ? -x : x; return ; }
typedef unsigned long long uLL; typedef long long LL;
struct Edge { int to; LL w; } ;
std::multiset<LL> st[50010]; std::vector<Edge> g[50010]; LL n, m, l = 1, ans, sum, max, r;
LL dfs(int x, int p, LL mid) { st[x].clear(); for (auto i : g[x]) { if (i.to != p) { LL val = dfs(i.to, x, mid) + i.w; if (val >= mid) ++sum; else st[x].insert(val); } } max = 0; while (!st[x].empty()) { if (st[x].size() == 1) return std::max(max, *st[x].begin()); std::multiset<LL>::iterator it = st[x].lower_bound(mid - *st[x].begin()); if (it == st[x].begin() && st[x].count(*it) == 1) ++it; if (it == st[x].end()) { max = std::max(max, *st[x].begin()); st[x].erase(st[x].find(*st[x].begin())); } else { ++sum; st[x].erase(st[x].find(*it)); st[x].erase(st[x].find(*st[x].begin())); } } return max; }
bool check(LL mid) { sum = 0; dfs(1, 0, mid); return sum >= m; }
int main() { read(n), read(m); for (LL i = 1, u, v, w; i < n; ++i) { read(u), read(v), read(w); r += w; g[u].push_back((Edge){v, w}), g[v].push_back((Edge){u, w}); } while (l <= r) { LL mid = (l + r) >> 1LL; if (check(mid)) { ans = mid, l = mid + 1; } else { r = mid - 1; } } printf("%lld\n", ans); return 0; }
|