最小生成树问题——小题三则


1、新的开始

题意:

一共 n n n 口矿井,为保证电力的供应有两种策略:

  • 在矿井 i i i 上建立一个发电站,费用为 v i v_i vi(发电站的输出功率可以供给任意多个矿井)。
  • 将这口矿井 i i i 与另外的已经有电力供应的矿井 j j j 之间建立电网,费用为 p i j p_{ij} pij

求保证所有矿井电力供应的最小花费方案。

分析:

如果只采取第二种策略,那么保证电力供应,也就是使得所有的点直接或者间接连通。为了权值最小,那便是最小生成树。

但是现在有第一种策略,自己建站。
如何在该条件下,将此题转化成最小生成树问题呢?

可以构建一个虚拟源点,超级发电站,如果有矿井要自己建站的话,就和虚拟源点连边。

于是,便是从整个图中挑选出若干条边,使得所有点直接或间接连通,便是最小生成树问题。

Code:
#include<bits/stdc++.h>
using namespace std;

#define int long long
#define fi first
#define se second
#define endl '\n'

const int N = 200010, mod = 1e9+7;
int T, n, m;
struct node{
	int x, y, w;
}a[N];
int pre[N];

bool cmp(node a, node b){
	return a.w < b.w;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

signed main(){
	Ios;
	cin>>n;
	
	for(int i=0;i<=n;i++) pre[i]=i;
	
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		int x;cin>>x;
		a[++cnt] = {i, 0, x};
	}
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			int x;cin>>x;
			if(j>i) a[++cnt] = {i, j, x};
		}
		
	sort(a+1, a+cnt+1, cmp);
	
	int ans=0;
	for(int i=1;i<=cnt;i++)
	{
		int x = a[i].x, y = a[i].y, w = a[i].w;
		if(find(x) != find(y)){
			ans += w;
			pre[find(x)] = find(y);
		}
	}
	cout << ans;
	
	return 0;
}

2、北极通讯网络

题意:

有 n 个村庄要实现两两村庄都可以直接或间接通讯,每座村庄的坐标用一对整数 (x,y) 表示。
现有两种通讯工具:无线电 和 卫星。
无线电数量不限,但是有距离限制;卫星数量有限,没有距离限制。

现分配给了 m 个卫星,计算出应该如何分配这 m 台卫星设备,才能使所配备的无线电的距离 d 值最小。输出最小值。

分析:

从第一句话看可能是最小生成树问题,但是怎么越往后看越不像了hh。

给出了每座村庄的坐标,所以可以求出任意两村庄直接的距离 dis。
如果要实现两个村庄的连通,要么使用使用两台卫星设备,要么用距离大于等于 dis 的无线电。

所以分配给的 m 台卫星设备可以实现 m-1 条边花费为0。

将所有的村庄连通需要 n-1 条边,那么需要配备的无线电距离 d 为这些边中的最长边。
但是现在可以免于考虑 m-1 条边,所以只需要按照 kruskal 求最小生成树的思路找到最小的 n-m 条边就行了。

Code:
#include<bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int,int>
#define fi first
#define se second

const int N = 200010, mod = 1e9+7;
int T, n, m;
PII pos[N];
struct node{
	int x, y;
	double w;
}a[N];
int pre[N];

bool cmp(node a, node b){
	return a.w < b.w;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

double dis(int a, int b, int c, int d)
{
	return sqrt((a-c)*(a-c) + (b-d)*(b-d));
}

signed main(){
	cin>>n>>m;
	
	for(int i=1;i<=n;i++) cin>>pos[i].fi>>pos[i].se;
	
	if(m>=n){
		cout << "0.00";return 0;
	}
	
	int cnt=0;
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			a[++cnt] = {i, j, dis(pos[i].first, pos[i].second, pos[j].first, pos[j].second)};
			
	sort(a+1, a+cnt+1, cmp);
	
	for(int i=1;i<=n;i++) pre[i] = i;
	
	int t = 0;
	double ans = 0;
	for(int i=1;i<=cnt;i++)
	{
		int x = a[i].x, y = a[i].y;
		double w = a[i].w;
		
		if(find(x) != find(y)){
			ans = w;
			pre[find(x)] = find(y);
			t++;
			
			if(t==n-1-(m-1)) break;
		}
	}
	printf("%.2f", ans);
	
	return 0;
}

3、走廊泼水节

题意:

给定一棵 N 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。

求增加的边的权值总和最小是多少。(边权为整数)

分析:

此题考察了 kruskal 求最小生成树的原理。

如何保证扩展后的图中,最小生成树不变?
在 kruskal 求解最小生成树问题时,每次选择最小的,两端点不在同一集合的一条边,将两个端点所在的集合合并。

这两个集合是用这个权值最小的边合并的,这个边会在最小生成树中。

但是如果出现了端点在这两个集合中,并且权值更小的边,那么这两个集合的合并就会用这个边,而不会用原来的那条边了,于是最小生成树就变了。同理,如果有权值相同的边,那么最小生成树就不唯一了。

所以,对于最小生成树的每条边来说,其应该是两个端点连接的两个集合中权值最小的一个边。
所以,从小到大遍历每条边 u,边权为w,将其两个端点所在的两个集合的任意两点连边:除去此边,还有 x*y-1 条边(x 和 y 为两个集合中元素个数)。
为了使得总权值最小,还要比该边权 w 大,那就把每条边的权值赋值为 w+1。

Code:
#include<bits/stdc++.h>
using namespace std;

#define int long long
#define fi first
#define se second

const int N = 200010, mod = 1e9+7;
int T, n, m;
struct node{
	int x, y, w;
}a[N];
int pre[N], siz[N];

bool cmp(node a, node b){
	return a.w < b.w;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

signed main(){
	Ios;
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<n;i++) cin>>a[i].x>>a[i].y>>a[i].w;
		
		sort(a+1, a+n, cmp);
		
		for(int i=1;i<=n;i++) pre[i] = i, siz[i] = 1;
	
		int ans = 0;
		for(int i=1;i<n;i++)
		{
			int x = a[i].x, y = a[i].y, w = a[i].w;
			ans += (w+1)*(siz[find(x)]*siz[find(y)]-1);
			siz[find(y)] += siz[find(x)];
			pre[find(x)] = find(y);
		}
		cout << ans <<endl;
	}
	
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值