文章目录
- 最便宜的构建
- 问题建模
- 问题分析
- 1.分析所求
- 2.方法1用并查集判断k个点集是否连通,不连通则由小到大添加边
- 代码
- 3. 方法2使用带权并查集维护当前集合所连通的点集个数
- 代码
- 4.方法3通过二分确定值
- 代码
最便宜的构建
问题建模
给定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;
}