[APIO2008]免费道路「并查集」

[APIO2008]免费道路「并查集」

题目描述

新亚(New Asia)王国有 \(N\) 个村庄,由 \(M\) 条道路连接。其中一些道路是鹅卵石路,而其它道路是水泥路。保持道路免费运行需要一大笔费用,并且看上去 王国不可能保持所有道路免费。为此亟待制定一个新的道路维护计划。国王已决定保持尽可能少的道路免费,但是两个不同的村庄之间都应该一条且仅由一条 且仅由一条免费道路的路径连接。同时,虽然水泥路更适合现代交通的需 要,但国王也认为走在鹅卵石路上是一件有趣的事情。所以,国王决定保持刚好 \(K\) 条鹅卵石路免费。

举例来说,假定新亚王国的村庄和道路如图 (\(a\))所示。如果国王希望保持两 条鹅卵石路免费,那么可以如图 (\(b\))中那样保持道路(\(1, 2\))、(\(2, 3\))、(\(3, 4\))和(\(3, 5\)) 免费。该方案满足了国王的要求,因为:(\(1\))两个村庄之间都有一条由免费道 路组成的路径;(\(2\))免费的道路已尽可能少;(\(3\))方案中刚好有两条鹅卵石道路 (\(2, 3\))和(3, 4)

图 : (\(a\))新亚王国中村庄和道路的一个示例。实线标注的是水泥路,虚线标注 的是鹅卵石路。(\(b\))一个保持两条鹅卵石路免费的维护方案。图中仅标出了免费道路。

给定一个关于新亚王国村庄和道路的述以及国王决定保持免费的鹅卵石 道路数目,写一个程序确定是否存在一个道路维护计划以满足国王的要求,如果 存在则任意输出一个方案。

输入格式

输入第一行包含三个由空格隔开的整数:\(N\),村庄的数目(\(1≤N≤20,000\));\(M\),道路的数目(\(1≤M≤100,000\));\(K\),国王希望保持免费的鹅卵石道路数目(\(0≤K≤N-1\))。

此后 M 行述了新亚王国的道路,编号分别为 \(1\)\(M\)。第(\(i+1\))行述了第 \(i\) 条 道路的情况。用 \(3\) 个由空格隔开的整数述:\(u_{i}\)\(v_{i}\)为第 \(i\) 条道路连接的两个村庄的编号,村庄编号为 \(1\)\(N\)\(c_{i}\),表示第 \(i\) 条道路的类型。
\(c_{i} = 0\) 表示第 \(i\) 条道路是鹅卵石路,\(c_{i} = 1\) 表 示第 i 条道路是水泥路。

输入数据保证一对村庄之间至多有一条道路连接

输出格式

如果满足国王要求的道路维护方案不存在,你的程序应该在输出第一行打印 \(no\) \(solution\)。 否则,你的程序应该输出一个符合要求的道路维护方案,也就是保持免费的 道路列表。按照输入中给定的那样输出免费的道路。如果有多种合法方案,你可 以任意输出一种。

思路分析

主要思想就是对\(Kruskal\)的应用

  • 要想保持最少的道路免费,只要选出\(n-1\)条路即可,保证这\(n-1\)条路可以使所有点连通,自然而然地要用到并查集
  • 关键是我们要恰好选出\(k\)条鹅卵石路,怎么处理?
  • 不难理解,\(k\)条鹅卵石路中,并不是所有路都是必须的,有一些是可以被代替的,而水泥路是可以随便选的,所以我们优先用水泥路跑一遍\(Kruskal\),再通过此时的连通性找出哪些鹅卵石路是必须的,将其特殊处理保证出现在答案中,然后再跑其他鹅卵石路直到有\(k\)条就可以了,这些鹅卵石路是可以随便选的,因为可以被代替,剩下的用水泥路补齐就彳出了
  • 最后就是判断有无解了,有两种情况:
    1. 必须的鹅卵石路超过了\(k\)
    2. 图无法连通

Code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
using namespace std;                  
const int MAXN = 20200;
const int MAXM = 100100;
int n, m, k, fa[MAXN], tot, cnt;
struct edge{  
    int u,v,w;    
}e[MAXM], ans[MAXM];  
bool cmp1(edge e1, edge e2) {
    return e1.w > e2.w;
}
bool cmp2(edge e1, edge e2) {
    return e1.w < e2. w;
}
int find(int x) {
    if(fa[x] == x) return x;
    else return fa[x] = find(fa[x]);
}
bool Union(int x, int y) { 
    x = find(x), y = find(y);
    if(x == y) return false;
    fa[x] = y;
    return true;
}
void init(){
    cnt = tot = 0;
    for(int i = 1; i <= n; i++) fa[i] = i;
}
void check(){
    int tmp = find(1);
    for(int i = 2; i <= n; i++){
        int f = find(i);
        if(f != tmp) {
            printf("no solution\n");
            return;
        }
        tmp = f;
    }
}
int main(){
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= m; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
    init();
    sort(e + 1, e + m + 1, cmp1);//先跑水泥路
    for(int i = 1; i <= m; i++)//找出必须的鹅卵石路,边跑Kruskal边判断
    	if(Union(e[i].u, e[i].v) && e[i].w == 0)tot++, e[i].w = -1; //权值改为-1,保证优先选   
        if(tot > k) {//必须的鹅卵石路超过了k条
        printf("no solution\n");
        return 0;
    }
    check();
    init(); 
    sort(e + 1, e + m + 1, cmp2);
    for(int i = 1; i <= m; i++) {
        int f1 = find(e[i].u), f2 = find(e[i].v);
        if(f1 == f2) continue;
        if(e[i].w == 1 || tot < k) {//鹅卵石路还不足k条就加进去,够了就直接选水泥路
            ans[++cnt] = e[i]; 
            fa[f1] = f2;
            if(e[i].w < 1)
            	tot++, e[i].w = 0;
        }
    }
    if(tot < k) {
        printf("no solution\n");
        return 0;
    }
    check();
    for(int i = 1; i <= cnt; i++) {
        if(ans[i].w == -1) ans[i].w = 0;
        printf("%d %d %d\n", ans[i].u, ans[i].v, ans[i].w);
    }
 	return 0;
}
posted @ 2020-07-22 17:35  HH_Halo  阅读(180)  评论(0编辑  收藏  举报