Loading

P5021 [NOIP2018 提高组] 赛道修建 (二分+树上贪心)

P5021 [NOIP2018 提高组] 赛道修建

upd 2024.11.14

求最大值最小,通常先从二分思考。

枚举完二分的值以后,也就限制了最小值的下届。我们要让满足下届的赛道尽可能多。

由于是树,解决这样的问题就有树上 dp 和贪心。

到底是哪一个?走 dp 的话,状态已经不好设计了,难以描述一个局面。

所以走贪心。树上贪心通常自底向上贪,因为每个子树最多向上给一条链,所以让子树内能拼的都拼完一定是最优的,不会等到上面再合并。拼完后就往上传剩下里面最优的。

怎么实现?直接用多重集维护当前链集是常见的手段。

在树上选 \(m\) 条不重合的路径(可以有交点),使得这些路径长度的最小值最大。

看到最小值最大,很自然想到二分模型:枚举最小值 \(L\),看大于等于 \(L\) 的路径能不能有 \(m\) 条。

如何在树上选出 \(m\) 条路径最优成为我们要思考的问题,考虑树上贪心。

普遍的思路是从叶子节点按子树合并,我们要让该子树对父亲的贡献最大,一定是在该子树已经无法拼出路径(贪心)后剩下的路径中取最大值(由于父亲唯一,所以只能留下最大值)。

\(f(u)\)\(u\) 子树内的节点到 \(u\) 节点的路径中无法拼出赛道的最长路径,我们已知所有的 \(f(v)+w(u,v)\),对于 \(f(v)+w(u,v)\ge L\) 的,直接贡献+1,对于剩下的,我们可以先让小的路径得到匹配,无法匹配的就更新 \(f(u)\)。这个过程可以用 multiset 维护。

复杂度为 \(O(n\log n\log\sum l)\)

总结:二分模型,树形贪心的基本思路,合适的数据结构维护。

#include <bits/stdc++.h>
typedef long long ll;
int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(isdigit(c)) {
		x = (x << 3) + (x << 1) + (c - '0');
		c = getchar();
	} 
	return x * f;
}
int n, m, cnt, L, ret, ans;
int h[50010], f[50010];
struct node {
	int to, nxt, w;
} e[100010];
void add(int u, int v, int w) {
	e[++cnt].to = v;
	e[cnt].nxt = h[u];
	e[cnt].w = w;
	h[u] = cnt;
} 
void dfs(int u, int fa) {
	std::multiset<int> s;
	for(int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(v == fa) continue;
		dfs(v, u);
		if(f[v] + e[i].w >= L) ret++;
		else s.insert(f[v] + e[i].w);  
	}
	while(!s.empty()) {
		std::multiset<int>::iterator it = s.begin();
		s.erase(it);
		std::multiset<int>::iterator it2 = s.lower_bound(L - *it);
		if(it2 == s.end()) {
			f[u] = std::max(f[u], *it);
		}
		else {
			s.erase(it2);
			ret++;
		}
	}
}
bool check(int x) {
	L = x;
	ret = 0;
	memset(f, 0, sizeof(f));
	dfs(1, 0);
	return ret >= m;
}
void Solve() {
	n = read(), m = read();
	int r = 0;
	for(int i = 1; i < n; i++) {
		int u = read(), v = read(), w = read();
		add(u, v, w), add(v, u, w);
		r += w;
	}
	int l = 0;
	r = r / m;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(check(mid)) l = mid + 1, ans = mid;
		else r = mid - 1; 
	}
	std::cout << ans << "\n";
}

int main() {
	
	Solve();

	return 0;
}
posted @ 2024-03-23 20:56  Fire_Raku  阅读(41)  评论(0编辑  收藏  举报