公路修建问题
题目链接:luogu P2323
题目大意
有 n 个点,m 条边,每条边有高费用和低费用。
然后你要选 n-1 条边使得每个点都连通,然后规定至少要有 k 条边选高费用,然后要你最小化最大费用的边的费用。(luogu 要输出其中一种合法方案,且是 m-1 条边)
思路
看到最小化最大费用自然想到二分答案。
然后你就考虑把高费用小于等于答案的边全部都试一次,能用就用,这个可以用并查集维护当前连通。
然后如果还是用不到
k
k
k 条就不行,然后接着是把低费用能用的都用一次,然后最后判断一下是否全部连通。(有效连接了
n
−
1
n-1
n−1 次就是全部连通)
然后至于方案你就那个边用了你就记录一下,然后最后输出就可以了。
代码
luogu 版
#include<cstdio>
#include<algorithm>
using namespace std;
struct node {
int x, y, c1, c2, num;
}a[200001];
struct answer {
int t, p;
}gz[10001], an[10001];
int n, k, m, fa[10001], ans, kill;
bool cmp(node x, node y) {
return x.c1 < y.c1;
}
bool cmpz(answer x, answer y) {
return x.t < y.t;
}
int find(int now) {
if (fa[now] == now) return now;
return fa[now] = find(fa[now]);
}
bool connect(int x, int y) {
int X = find(x), Y = find(y);
if (X == Y) return 0;
fa[X] = Y; return 1;
}
bool ck(int mid) {
int now = 1;
kill = 0;
for (int i = 1; i <= n; i++) fa[i] = i;
while (now <= m) {
if (a[now].c1 > mid) break;//能建高费用的全部丢进去跑并查集
if (connect(a[now].x, a[now].y)) {
kill++;
gz[kill].t = a[now].num;
gz[kill].p = 1;
}
now++;
}
if (kill < k) return 0;//高费用的边不够
for (int i = now; i <= m; i++)//剩下的低费用也是能选就丢进去跑并查集
if (a[i].c2 <= mid) {
if (connect(a[i].x, a[i].y)) {
kill++;
gz[kill].t = a[i].num;
gz[kill].p = 2;
}
if (kill == n - 1) return 1;
}
if (kill == n - 1) return 1;
return 0;
}
int main() {
scanf("%d %d %d", &n, &k, &m);
for (int i = 1; i < m; i++) {
scanf("%d %d %d %d", &a[i].x, &a[i].y, &a[i].c1, &a[i].c2);
a[i].num = i;
}
sort(a + 1, a + m + 1, cmp);
int l = 0, r = 30000;//二分
while (l <= r) {
int mid = (l + r) >> 1;
if (ck(mid)) {ans = mid, r = mid - 1; for (int i = 1; i < n; i++) an[i] = gz[i];}
else l = mid + 1;
}
sort(an + 1, an + (n - 1) + 1, cmpz);//把操作按时间排序
printf("%d\n", ans);
for (int i = 1; i < n; i++)
printf("%d %d\n", an[i].t, an[i].p);
return 0;
}
jzoj 版
(由于这个不用输出方案,其它都一样,就不注释了)
#include<cstdio>
using namespace std;
const int N = 10005;
struct Line {
int from, to, c1, c2;
}a[N<<1];
int n, m, k, ans;
int fa[N];
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int check(int mid) {
for (int i = 1; i <= n; ++i) fa[i] = i;
int cnt = 0;
for (int i = 1; i <= m; ++i) {
if (a[i].c1 > mid) continue;
int x = find(a[i].from), y = find(a[i].to);
if (x != y) fa[x] = y, ++cnt;
}
if (cnt < k) return 0;
for (int i = 1; i <= m; ++i) {
if (a[i].c2 > mid) continue;
int x = find(a[i].from), y = find(a[i].to);
if (x != y) fa[x] = y, ++cnt;
}
if (cnt < n - 1) return 0;
return 1;
}
int main() {
scanf("%d %d %d", &n, &k, &m);
for (int i = 1; i <= m; ++i)
scanf("%d %d %d %d", &a[i].from, &a[i].to, &a[i].c1, &a[i].c2);
int l = 0, r = 30000;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", ans);
}