[树] [贪心] CF1976F Remove Bridges

posted on 2024-06-24 05:22:15 | under | source

upd:差不多一年后重新看这题,发现完全是套路啊。

题意:\(n\) 个点的树,以 \(1\) 为根,且 \(1\) 只有一个儿子。可以加 \(k\) 条边,要满足割边下端的子树内不存在非割边,对 \(k\in [1,n)\),求最少割边数量。

考虑加入边 \((u,v)\),等价于让树上 \(u\to v\) 路径的所有边变成非割边。那么这就变成了染色问题,选择 \(k\) 条路径染色,满足未被染色的边下面的所有边也未被染色,并且染色边尽量多。

可以发现,选取路径的两端必然是叶子节点或 \(1\),否则可以不断向下伸长路径,显然这不会影响合法性。因为从完整的子树中抽出一条顶端到叶子的链,剩下的也是完整子树。

注意到 \(1\) 只有一个儿子,所以 \(k=1\) 的答案是 \(1\) 到叶子的一条最长链。

原思路

对于一般情况,可以发现原树被已染色的边,划分为多个完整的、未被染色的子树,不难想到贪心:从中选取两条最长链拼接,最长是指未被染色的边尽量多。

假如选取的两条链来自不同子树,那么其构成的路径一定合法。但是来自同一棵子树怎么办?假设之前操作了 \((u,v)\),现在想操作 \((x,y)\),可以发现,更改操作为 \((u,x),(v,y)\) 后,就变得合法了。

所以可以拿个优先队列存剩下所有子树的最长链,除第一次以外,均选 \(2\) 条最长链即可。由于每个点只被遍历一次、加入一次,所以复杂度 \(O(n\log n)\)

不难感性理解,口胡下正确性:交换(时间上)相邻两条链的选取顺序,不会影响合法性,所以可以交换使得选取的链长递减,即每次选最长链。

新思路

无论怎么操作,操作端点集合一定是至多 \(2k\) 个叶子。有经典结论:可以用不超过 \(k\) 条链,恰好覆盖 \(2k\) 个叶子的虚树。这是覆盖点数的上界了,同时一定满足题意(以虚树的视角看待,就是链上挂着一些完整子树),所以这么干。

这题甚至傻乎乎地告诉你 \(1\) 一定被操作,那就更容易了,经典长剖贪心即可。\(O(n\log n)\)\(O(n)\)

代码

#include<bits/stdc++.h>
using namespace std;

#define pir pair<int, int>
const int N = 3e5 + 5;
int T, n, k, u, v, fa[N], ans;
int f[N], leaf[N]; 
vector<int> to[N];
struct node{int u, leaf, w;};
inline bool operator < (const node& A, const node& B) {return A.w < B.w;}
priority_queue<node> s; 

inline void init(int u){
	f[u] = 0, leaf[u] = u;
	for(auto v : to[u])
		if(v ^ fa[u]){
			fa[v] = u, init(v);
			if(f[v] + 1 > f[u]) f[u] = f[v] + 1, leaf[u] = leaf[v]; 
		} 
}
inline void upd(node p){
	int u = p.u, pos = p.leaf, lst = -1; ans -= p.w;
	while(lst != u){
		for(auto v : to[pos])
			if(v != lst && v != fa[pos]) s.push({v, leaf[v], f[v] + 1});
		lst = pos, pos = fa[pos];
	}
}
inline void doit(){
	if(!s.empty()){
		node A = s.top(); s.pop();
		upd(A);
	}
}
signed main(){
	cin >> T;
	while(T--){
		while(!s.empty()) s.pop();
		scanf("%d", &n), ans = n - 1;
		for(int i = 1; i <= n; ++i) to[i].clear();
		for(int i = 1; i < n; ++i) scanf("%d%d", &u, &v), to[u].push_back(v), to[v].push_back(u);
		init(1), s.push({1, leaf[1], f[1]});
		for(int i = 1; i < n; ++i){
			doit();
			if(i != 1) doit();
			printf("%d ", ans);
		}
		printf("\n");
	}
	return 0;
}
posted @ 2026-01-13 11:16  Zwi  阅读(0)  评论(0)    收藏  举报