树上贪心+二分
树上贪心+二分
题目描述
牛客周赛 Round 85
小红拿到了一棵由 \(n\)个节点组成的树,她已经把所有节点都染成了红色。这时,小紫准备将 k 个节点染成紫色,使得最大红色连通块的大小尽可能小。你能帮帮她吗?
$ \hspace{15pt}$对于树上的两个点,如果它们均为红色且相连,则称他们位于同一个红色连通块里。特别地,一个单独的点也构成一个红色连通块。连通块的大小即为连通块中节点的数量。
输入描述
\(\hspace{15pt}\)第一行输入两个正整数$ n,k(1≦k≦n≦105)\(,代表节点数量和小紫准备染色的次数。 \)\hspace{15pt}$此后 \(n−1\)行,第 iii 行输入两个正整数$ u_i,v_i(1≦u_i,v_i≦n)$,代表第 \(i\)条边连接节点\(u_i\) 和节点 \(v_i\)。保证输入的图是一棵树。
输出描述
一个整数,代表最大红色连通块大小的最小值。特殊的,如果不存在红色连通块,请输出 0。
题解
因为题目给定的加粗信息,要求的是一个最大值的最小值,很明显我们要往二分方向想
又因为k次最大ans,则k + 1次一定是 <= ans,满足单调性
- 二分最大连通块的数量mid
- 跑一遍DFS,以1节点为根节点,后序遍历,求出每个点的孩子节点个数,如果该点 > mid,则染为紫色= 0
- 最后输出二分的结果
这里要注意的是,dfs遍历的时候无论以那个点为根节点都可以,因为染色的点是固定的
贪心的思想就是,尽量不染叶子节点。
#include <bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define ppb pop_back
#define SZ(v) ((int)v.size())
#define pii pair<int, int>
#define int long long
#define all(v) v.begin(), v.end()
#define debug(x) cout << "======" << x << "========" << "\n"
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef double db;
using namespace std;
const int N = 1e6+10;
const int mod = 1e9+7;
int _;
int n, k;
void solve() {
cin >> n >> k;
vector<vector<int>> e(n+1);
for(int i = 0, u, v; i < n - 1; i++) {
cin >> u >> v;
e[u].pb(v);
e[v].pb(u);
}
auto check = [&] (int m) {
int need = 0;
vector<int> sz(n + 1, 0);
function<void(int, int)> dfs = [&] (int u, int fa) {
for(int v : e[u]) {
if(v == fa) continue;
dfs(v, u);
sz[u] += sz[v];
}
sz[u]++;
if(sz[u] > m) {
sz[u] = 0;
need++;
}
};
dfs(1, 0);
return need <= k;
};
int l = 0, r = n;
int ans = 0;
while(l <= r) {
int m = (l + r) >> 1;
if(check(m)) {
ans = m;
r = m - 1;
} else {
l = m + 1;
}
}
cout << ans;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
_ = 1;
// cin >> _;
while(_--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号