牛客小白月赛74 F题解

最便宜的构建

在这里插入图片描述在这里插入图片描述

问题建模

给定n个点m条边的带权无向图,以及k个点集,求选择一些边,使得k个点集内的所有点都连通的边集中最大边权最小的值为多少。

问题分析

1.分析所求

所求的边集首先要满足使得k个点连通,其次让所选边集中的最大值尽可能小。则对于所有的边首先得按权值从小到大考虑,使用了当前边能否连通k个点集。

2.方法1用并查集判断k个点集是否连通,不连通则由小到大添加边

代码
#include<bits/stdc++.h>

#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =1e5+10, Mod = 998244353, P = 2048;
struct edge{
    int u,v,d;
};
edge e[N];
bool st[N];
int p[N];
int a[N];
int idx;
int n,m;
bool cmp(edge e1,edge e2){
    return e1.d<e2.d;
}
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}

void solve() {
    cin >>n >>m;
    for(int i=0;i<m;i++){
        int u,v,d;
        scanf("%d %d %d",&u,&v,&d);
        e[i]={u,v,d};
    }
    int k;
    cin>>k;
    for(int i=0;i<k;i++){
        int s;
        cin >>s;
        for(int j=0;j<s;j++){
            int x;
            scanf("%d",&x);
            ///将k个点集中所需的点都存起来,方便使用
            if(!st[x])  st[x]=true,a[++idx]=x;
        }
    }   

    sort(e,e+m,cmp);
    for(int i=1;i<=n;i++)   p[i]=i;
    int pos=0;
    ///枚举k个点集内的点,若有不连通则按权值由小到大添加边
    for(int i=2;i<=idx;i++){
        while(find(a[i])!=find(a[i-1])){
            int fa=find(e[pos].u),fb=find(e[pos].v);
            if(fa!=fb)  p[fa]=fb;
            pos++;
        }
    }
    cout <<(pos?e[pos-1].d:0) <<endl;
}   


int main() {
    int t = 1;
    //cin >> t;
    while (t--) solve();
    return 0;
}

3. 方法2使用带权并查集维护当前集合所连通的点集个数

代码
#include<bits/stdc++.h>

#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =1e5+10, Mod = 998244353, P = 2048;
struct edge{
    int u,v,d;
};
edge e[N];
int d[N],p[N];
int cnt,n,m;

bool cmp(edge e1,edge e2){
    return e1.d<e2.d;
}
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}

int check(){
    sort(e,e+m,cmp);
    for(int i=1;i<=n;i++)   p[i]=i;

    int res=0;
    if(cnt<=1)  return res;
    for(int i=0;i<m;i++){
        int fa=find(e[i].u),fb=find(e[i].v);
        if(fa==fb)  continue;
        d[fb]+=d[fa];
        p[fa]=fb;
        res=e[i].d;
        ///若当前点所在集合内包含的所需点数量等于所需点个数,则表示k个点集已连通
        if(d[fb]==cnt)  break;
    }

    return res;
}

void solve() {
    cin >>n >>m;
    for(int i=0;i<m;i++){
        int u,v,d;
        scanf("%d %d %d",&u,&v,&d);
        e[i]={u,v,d};
    }
    int k;
    cin>>k;
    for(int i=0;i<k;i++){
        int s;
        cin >>s;
        for(int j=0;j<s;j++){
            int x;
            scanf("%d",&x);
            ///将该点的权值设为1,代表以该点为祖宗结点的集合内有1个所需点
            if(!d[x])  d[x]=1,cnt++;
        }
    }   
    cout <<check() <<"\n";
}   


int main() {
    int t = 1;
    //cin >> t;
    while (t--) solve();
    return 0;
}

4.方法3通过二分确定值

由于我们是从小到大考虑使用当前边后能否连通k个点集,则可以考虑通过二分的方法,先确定一个边权,然后检查使用小于等于该边权的边能否将k个点集都连通,是否连通则采用并查集来处理。

代码
#include<bits/stdc++.h>

#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =1e5+10, Mod = 998244353, P = 2048;
struct edge{
    int u,v,d;
};
edge e[N];
bool st[N];
int p[N];
int a[N];
int idx;
int n,m;
bool cmp(edge e1,edge e2){
    return e1.d<e2.d;
}
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}

bool check(int x){
    for(int i=1;i<=n;i++)   p[i]=i;

    for(int i=0;i<m;i++){
        if(e[i].d>x)    break;
        int pa=find(e[i].u),pb=find(e[i].v);
        if(pa!=pb)  p[pa]=pb;
    }
	
    ///判断k个点集内的点是否都连通
    for(int i=2;i<=idx;i++){
        if(find(a[1])!=find(a[i])){
            return false;
        }
    }

    return true;
}

void solve() {
    cin >>n >>m;
    for(int i=0;i<m;i++){
        int u,v,d;
        scanf("%d %d %d",&u,&v,&d);
        e[i]={u,v,d};
    }
    int k;
    cin>>k;
    for(int i=0;i<k;i++){
        int s;
        cin >>s;
        for(int j=0;j<s;j++){
            int x;
            scanf("%d",&x);
            ///将k个点集中所需的点都存起来,方便使用
            if(!st[x])  st[x]=true,a[++idx]=x;
        }
    }   

    sort(e,e+m,cmp);
    int l=0,r=1e9;
    while(l<r){
        int mid=(l+r)>>1;
        if(check(mid))  r=mid;
        else l=mid+1;
    }

    cout <<l <<"\n";
}   


int main() {
    int t = 1;
    //cin >> t;
    while (t--) solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值