1. 程式人生 > >藍橋杯 城市建設 (Kruskal)

藍橋杯 城市建設 (Kruskal)

問題描述

棟棟居住在一個繁華的 C 市中,然而,這個城市的道路大都年久失修。市長準備重新修一些路以方便市民,於是找到了棟棟,希望棟棟能幫助他。

C 市中有 n 個比較重要的地點,市長希望這些地點重點被考慮。現在可以修一些道路來連線其中的一些地點,每條道路可以連線其中的兩個地點。另外由於 C 市有一條河從中穿過,也可以在其中的一些地點建設碼頭,所有建了碼頭的地點可以通過河道連線。

棟棟拿到了允許建設的道路的資訊,包括每條可以建設的道路的花費,以及哪些地點可以建設碼頭和建設碼頭的花費。

市長希望棟棟給出一個方案,使得任意兩個地點能只通過新修的路或者河道互達,同時花費盡量小。

輸入格式

輸入的第一行包含兩個整數 n, m,分別表示 C 市中重要地點的個數和可以建設的道路條數。所有地點從 1 到 n 依次編號。

接下來 m 行,每行三個整數 a, b, c,表示可以建設一條從地點 a 到地點 b 的道路,花費為 c 。若 c 為正,表示建設是花錢的,如果 c 為負,則表示建設了道路後還可以賺錢(比如建設收費道路)。

接下來一行,包含 n 個整數 w_1, w_2, …, w_n 。如果 w_i 為正數,則表示在地點i建設碼頭的花費,如果 w_i 為 -1 ,則表示地點i無法建設碼頭。

輸入保證至少存在一個方法使得任意兩個地點能只通過新修的路或者河道互達。

輸出格式

輸出一行,包含一個整數,表示使得所有地點通過新修道路或者碼頭連線的最小花費。如果滿足條件的情況下還能賺錢,那麼你應該輸出一個負數。

樣例輸入

5 5
1 2 4
1 3 -1
2 3 3
2 4 5
4 5 10
-1 10 10 1 1

樣例輸出

9

思路

先說一下這道題的坑點吧!

原題中 市長希望棟棟給出一個方案,使得任意兩個地點能只通過新修的路或者河道互達,同時花費盡量小。 這句話很容易讓人誤解為圖中兩點之間只能有一條路徑,於是我就神奇的 WA 了。(一定要加上所有的負權邊)

對於有碼頭的城市,我們為其建立一條從

i0 的邊,此時答案分兩種情況:

  • 結果只有陸路,不包含水路,即舍掉 0 號點,此時按照 Kruskal 演算法的思想,貪心找出連通所有城市且總花費最低的方案,記為 ans1
  • 結果包含水路,依然按照 Kruskal 演算法的思想,找出連通所有城市以及 0 號點的最小花費方案,記為 ans2

則最終的答案為: min(ans1,ans2)

AC 程式碼

#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
#include <set>
#include <map>
#include <stack>

#define IO                       \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0);

using namespace std;
const int maxn = 1e6 + 10;
typedef pair<int,int> P;

struct node{
    int from;
    int to;
    int cost;
    int next;
    friend bool operator<(const node &x,const node &y){
        return x.cost < y.cost;
    }
}edge[maxn];
int head[maxn],tot;
int n,m;

int fa[maxn],rk[maxn];

void init(){
    memset(head,-1,sizeof(head));
    tot = 0;
}

void addedge(int u,int v,int cost){
    edge[tot].from = u;
    edge[tot].to = v;
    edge[tot].cost = cost;
    edge[tot].next = head[u];
    head[u] = tot++;
}

void init_set(){
    memset(rk,0,sizeof(rk));
    for(int i=0;i<maxn;i++){
        fa[i] = i;
    }
}

int find_set(int x){
    if(fa[x]!=x)
        fa[x] = find_set(fa[x]);
    return fa[x];
}

bool union_set(int x,int y){
    x = find_set(x);
    y = find_set(y);
    if(x == y)return false;
    if(rk[x]>rk[y])
        fa[y] = x;
    else{
        fa[x] = y;
        if(rk[x] == rk[y])
            ++rk[y];
    }
    return true;
}

int result1(){  // 包含 0 號點
    init_set();
    int sum = 0;
    for(int i=0;i<tot;i++){
        int from = edge[i].from;
        int to = edge[i].to;
        bool flag = union_set(from,to);
        if(flag || edge[i].cost < 0){   // 負權邊一定要加
            sum += edge[i].cost;
        }
    }
    return sum;
}

int result2(){  // 不包含 0 號點
    init_set();
    int sum = 0;
    for(int i=0;i<tot;i++){
        int from = edge[i].from;
        int to = edge[i].to;
        if(from == 0 || to == 0)continue;
        bool flag = union_set(from,to);
        if(flag || edge[i].cost < 0){   // 負權邊一定要加
            sum += edge[i].cost;
        }
    }
    int cnt = 0;
    for(int i=1;i<=n;i++){
        if(find_set(i)==i)++cnt;
    }
    if(cnt != 1)return 1e9;
    return sum;
}

int main(){
    IO;
    init();
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int u,v,c;
        cin>>u>>v>>c;
        addedge(u,v,c);
    }
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        if(x!=-1){
            addedge(i,0,x);
        }
    }
    sort(edge,edge+tot);
    cout<<min(result1(),result2())<<endl;
    return 0;
}