「洛谷 P2619」Tree I - 二分答案 + 最小生成树

题目描述

洛谷链接

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 \(need\) 条白色边的生成树。

题目保证有解。

解题思路

显然这道题靠的是最小生成树。而要求有 \(\text{need}\) 条白色边。于是我们可以在白色边上操作。

我们可以将所有的白边同时加上或减去一个权值,使最小生成树的边的选择产生变化,直到选择的边中恰有 \(\text{need}\) 条白色边。

于是我们可以二分答案白边加上或减去的权值。在利用 Kruskal 求出最小生成树的同时统计白边的数量。通过调整白边权值变化达到生成树中恰有 \(\text{need}\) 条白边且权值最小即可。

代码演示

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
81
82
83
#include <cstdio>
#include <algorithm>

const int MAXN = 5e4;
const int MAXM = 1e5;
const int INF = 100;

struct Edge {
int s, t, c, col;

bool operator<(const Edge &other) const {
if (c == other.c) return col < other.col;
return c < other.c;
}
} E[MAXM];

struct UFS {
int f[MAXN];

void init(int n) {
for (int i = 0; i < n; i++) f[i] = i;
}

int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}

void merge(int x, int y) {
f[find(x)] = find(y);
}
} ufs;

int v, e, need;

inline std::pair<int, int> kruskal() {
ufs.init(v);
std::sort(E, E + e);

int ans = 0, counts = 0, cntNeed = 0;

for (int i = 0; i < e; i++) {
Edge &edge = E[i];
if (ufs.find(edge.s) == ufs.find(edge.t)) continue;
ufs.merge(edge.s, edge.t);
ans += edge.c;
if (edge.col == 0) cntNeed++;
if (++counts == v - 1) break;
}

return std::make_pair(cntNeed, ans);
}

int main() {
scanf("%d %d %d", &v, &e, &need);
for (int i = 0; i < e; i++) {
scanf("%d %d %d %d", &E[i].s, &E[i].t, &E[i].c, &E[i].col);
}

int l = -INF, r = INF, ans = 0;
while (l <= r) {
int mid = (l + r) >> 1;

for (int i = 0; i < e; i++) {
if (E[i].col == 0) {
E[i].c += mid;
}
}
std::pair<int, int> res = kruskal();
res.second -= mid * need;
for (int i = 0; i < e; i++) {
if (E[i].col == 0) {
E[i].c -= mid;
}
}

if (res.first >= need) l = mid + 1, ans = res.second;
else r = mid - 1;
}

printf("%d\n", ans);

return 0;
}