【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。但清晰地思路换来简短的代码

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