1. 程式人生 > 實用技巧 >[APIO2008]免費道路「並查集」

[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++)//找出必須的鵝卵石路
    	if(Union(e[i].u, e[i].v) && e[i].w == 0)tot++, e[i].w = -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;
}