BZOJ 3624: [Apio2008]免费道路(贪心+并查集)

6 篇文章 0 订阅
6 篇文章 0 订阅

传送门

题目大意:给一个图,有黑边和白边,输出一种生成树方案使得白边恰有k条,可能无解。

没错,这是道水题。根据tutu的话,我们用LCT来维护。如果我们直接选白色,随便选k条,再全选黑色,可能使得图不连通。因为有些白色的边是必选的。

尽量选黑边,一遍生成树找出必选的白边。然后摧毁它,重建生成树。将必选的先放进去,如果不足k条,那就随便加够它,最后选黑边即可。

这样贪心,一开始我们选出了那些黑边不能代替的白边,然后剩下的所有白边一定能被黑边给替代了。我们加够k条,相当于将原来的树加成环再去掉一条黑边。所以最后一定能够构成一棵生成树,利用了生成树上强行加边去掉环上另一条边即可的性质。做生成树用并查集就行了。

无解的情况包括一开始基图不连通,必须要选的白边超过k条,或者选不够k条白边,或者强选k条白边后无法连通了(可能有多余的乱判=。=)。

代码:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#define maxn 20100
#define maxm 100100 

using namespace std;


int n, m, k, Fa[maxn];

struct Data{
    int x, y, z;
}E[maxm];

bool cmp1(Data A, Data B){
    return A.z > B.z;
}

bool cmp2(Data A, Data B){
    return A.z < B.z;
}

int FindR(int x){
    if(Fa[x] == x)  return x;
    return Fa[x] = FindR(Fa[x]);
}

bool used[maxm];

int main(){

    scanf("%d%d%d", &n, &m, &k);

    for(int i = 1; i <= m; i++)
        scanf("%d%d%d", &E[i].x, &E[i].y, &E[i].z);

    sort(E+1, E+m+1, cmp1);

    for(int i = 1; i <= n; i++)  Fa[i] = i;

    int cnt = 0;

    for(int i = 1; i <= m; i++){
        int r1 = FindR(E[i].x), r2 = FindR(E[i].y);
        if(r1 != r2){
            Fa[r1] = r2;
            if(!E[i].z){  
                E[i].z = -1;
                cnt ++;
            }
        }       
    }

    if(cnt > k){  
        puts("no solution");
        return 0;
    }

    Fa[1] = FindR(1);
    for(int i = 2; i <= n; i++){
        Fa[i] = FindR(i);
        if(Fa[i] != Fa[i-1]){
            puts("no solution");
            return 0;
        }
    }

    for(int i = 1; i <= n; i++)  Fa[i] = i;

    sort(E+1, E+m+1, cmp2);

    for(int i = 1; i <= m; i++){
        int r1 = FindR(E[i].x), r2 = FindR(E[i].y);
        if(r1 != r2){
            if(E[i].z == 1 || k){  
                used[i] = true;
                Fa[r1] = r2;
                if(E[i].z != 1){
                    k --;
                    E[i].z = 0;
                }
            }
        }
    }

    if(k){
        puts("no solution");
        return 0;
    }

    Fa[1] = FindR(1);
    for(int i = 2; i <= n; i++){
        Fa[i] = FindR(i);
        if(Fa[i] != Fa[i-1]){
            puts("no solution");
            return 0;
        }
    }

    for(int i = 1; i <= m; i++)
        if(used[i])
            printf("%d %d %d\n", E[i].x, E[i].y, E[i].z);

    return 0;
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值