【APIO2008】免費道路

前言

看了同學的貪心博客來做此題,同學都說水。然而我把問題看錯了。。。

題目描述

新亞(New Asia)王國有 N 個村莊,由 M 條道路連接。其中一些道路是鵝卵石路,而其它道路是水泥路。保持道路免費運行需要一大筆費用,並且看上去 王國不可能保持所有道路免費。爲此亟待制定一個新的道路維護計劃。

國王已決定保持儘可能少的道路免費,但是兩個不同的村莊之間都應該一條且僅由一條 且僅由一條免費道路的路徑連接。同時,雖然水泥路更適合現代交通的需 要,但國王也認爲走在鵝卵石路上是一件有趣的事情。所以,國王決定保持剛好 K 條鵝卵石路免費。

舉例來說,假定新亞王國的村莊和道路如圖 3(a)所示。如果國王希望保持兩 條鵝卵石路免費,那麼可以如圖 3(b)中那樣保持道路(1, 2)、(2, 3)、(3, 4)和(3, 5) 免費。該方案滿足了國王的要求,因爲:(1)兩個村莊之間都有一條由免費道 路組成的路徑;(2)免費的道路已儘可能少;(3)方案中剛好有兩條鵝卵石道路 (2, 3)和(3, 4)

圖 3: (a)新亞王國中村莊和道路的一個示例。實線標註的是水泥路,虛線標註 的是鵝卵石路。(b)一個保持兩條鵝卵石路免費的維護方案。圖中僅標出了免 費道路。

給定一個關於新亞王國村莊和道路的述以及國王決定保持免費的鵝卵石 道路數目,寫一個程序確定是否存在一個道路維護計劃以滿足國王的要求,如果 存在則任意輸出一個方案。

輸入輸出格式

輸入

第一行包含三個由空格隔開的整數:

N,村莊的數目(1≤N≤20,000);

M,道路的數目(1≤M≤100,000);

K,國王希望保持免費的鵝卵石道路數目(0≤K≤N - 1)。

此後 M 行述了新亞王國的道路,編號分別爲 1 到 M。第(i+1)行述了第 i 條 道路的情況。用 3 個由空格隔開的整數述:

ui 和 vi,爲第 i 條道路連接的兩個村莊的編號,村莊編號爲 1 到 N;

ci,表示第 i 條道路的類型。ci = 0 表示第 i 條道路是鵝卵石路,ci = 1 表 示第 i 條道路是水泥路。

輸入數據保證一對村莊之間至多有一條道路連接輸入格式

輸出格式

如果滿足國王要求的道路維護方案不存在,你的程序應該在輸出第一行打印 no solution。 否則,你的程序應該輸出一個符合要求的道路維護方案,也就是保持免費的 道路列表。按照輸入中給定的那樣輸出免費的道路。如果有多種合法方案,你可 以任意輸出一種。

輸入輸出樣例

輸入樣例#1:

5 7 2
1 3 0
4 5 1
3 2 0
5 3 1
4 3 0
1 2 1
4 2 1

輸出樣例#1:

3 2 0
4 3 0
5 3 1
1 2 1

解法

顯然最終免費道路就是構成一棵樹。所以只要用圖求一顆生成樹。想法不難。我重點想表達的是怎麼樣寫碼量少,建樹次數少(聽說好多人都建三次樹)。
只要排除了所有no_solution的情況剩下的就是答案。
no_solution情況有
1. 建不成一棵樹
2. 無論如何用k個0邊再配1邊都建不成一棵樹

顯然我們要把0邊與1邊分開考慮。所以排個序。再接下來,由於我們發現不能確定是哪k條0邊,所以一開始先枚舉1邊.這樣我們就能用1邊構成此圖的生成樹或是聯通塊最少。當我們用所有的1邊構成最少聯通塊後就可以去在這個基礎上補0邊。注意,如果補得0邊超過k,那麼無解的,因爲1邊都用完了,這是最少要用的0邊。我們現在可以考慮那些補進去得0邊。他們只可能少了,所以要做的就是刪掉一些剛纔放進去的1邊。那也就是說,在剛纔放進去的0邊的基礎上再加一些0邊。這樣那就再做一次生成樹。把剛纔放進去的0邊放進去,這些0邊肯定要選入答案。在枚舉剛纔沒有放進去的0邊這些0邊最大限度的代替1邊。然後再枚舉1邊加入。

代碼

#include<bits/stdc++.h>
using namespace std;
inline char gc(){
    static char buf[1<<5],*p1=buf,*p2=buf;
    return (p2==p1)&&(p2=(p1=buf)+fread(buf,1,1<<5,stdin),p1==p2)?EOF:*p1++;
}
template <class T>
inline void read(T&data){
    data=0;
    register char ch=0;
    while(ch<'0'||ch>'9')ch=gc();
    while(ch<='9'&&ch>='0'){
        data=(data<<3)+(data<<1)+(ch^48);
        ch=gc();
    }
    return;
}const int _ = 1e5+1e2;
struct edge {
    int u,v,ki;
}e[_],ans[110000];
bool pd[_];
int parent[_],tot,n,k,m,cnt;
inline int fi(register int x){return x==parent[x]?x:parent[x]=fi(parent[x]);}
inline bool cmp1(register edge x,register edge y){
    return x.ki>y.ki;
}
inline void print(){
    puts("no solution");exit(0);
}
bool cmp2(register edge x,register edge y){
    return x.ki<y.ki;
}
int main(){
    read(n),read(m),read(k);
    for(register int i=1,a,b,c;i<=m;++i){
        read(a),read(b),read(c);
        e[i].u=a,e[i].v=b,e[i].ki=c;    
    }
    sort(e+1,e+m+1,cmp1);
    for(register int i=1;i<=n;++i)parent[i]=i;tot=0;
    for(register int i=1;i<=m;++i){
        register int fu=fi(e[i].u),fv=fi(e[i].v);
        if(fu==fv)continue;
        if(e[i].ki==0){
            ++tot;
            e[i].ki=-1;
        }
        parent[fu]=fv;

    }
    if(tot>k)print();
    for(register int i=2,gh=fi(1);i<=n;++i){

        if(fi(i)!=gh)print();
    }
    for(register int i=1;i<=n;++i)parent[i]=i;
    sort(e+1,e+m+1,cmp2);tot=0;
    for(register int i=1;i<=m;++i){
        register int fu=fi(e[i].u),fv=fi(e[i].v);
        if(fu==fv)continue;
        if(e[i].ki==1||tot<k){
            ans[++cnt]=e[i];
            parent[fu]=fv;
            if(e[i].ki<1)++tot;
        }
    }
    if(tot<k){print();}
    for(register int i=1;i<=cnt;++i){
        if(ans[i].ki==-1)ans[i].ki=0;
        printf("%d %d %d\n",ans[i].u,ans[i].v,ans[i].ki);
    }
}

總結

這道題思路簡單,亂搞可以AC。但清晰地思路換來簡短的代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章