问题 1437: [蓝桥杯][历届试题]城市建设(最小生成树)

题目描述
栋栋居住在一个繁华的C市中,然而,这个城市的道路大都年久失修。市长准备重新修一些路以方便市民,于是找到了栋栋,希望栋栋能帮助他。

C市中有n个比较重要的地点,市长希望这些地点重点被考虑。现在可以修一些道路来连接其中的一些地点,每条道路可以连接其中的两个地点。另外由于C市有一条河从中穿过,也可以在其中的一些地点建设码头,所有建了码头的地点可以通过河道连接。

栋栋拿到了允许建设的道路的信息,包括每条可以建设的道路的花费,以及哪些地点可以建设码头和建设码头的花费。

市长希望栋栋给出一个方案,使得任意两个地点能只通过新修的路或者河道互达,同时花费尽量小。

样例说明
建设第2、3、4条道路,在地点4、5建设码头,总的花费为9。
数据规模和约定
对于100%的数据,1 < = n < = 10000,1 < = m < = 100000,-1000< =c< =1000,-1< =w_i< =1000,w_i≠0。

输入
输入的第一行包含两个整数n, m,分别表示C市中重要地点的个数和可以建设的道路条数。所有地点从1到n依次编号。
接下来m行,每行三个整数a, b, c,表示可以建设一条从地点a到地点b的道路,花费为c。若c为正,表示建设是花钱的,如果c为负,则表示建设了道路后还可以赚钱(比如建设收费道路)。
接下来一行,包含n个整数w_1, w_2, …, w_n。如果w_i为正数,则表示在地点i建设码头的花费,如果w_i为-1,则表示地点i无法建设码头。
输入保证至少存在一个方法使得任意两个地点能只通过新修的路或者河道互达。
输出
输出一行,包含一个整数,表示使得所有地点通过新修道路或者码头连接的最小花费。如果满足条件的情况下还能赚钱,那么你应该输出一个负数。
样例输入
5 5
1 2 4
1 3 -1
2 3 3
2 4 5
4 5 10
-1 10 10 1 1
样例输出
9
思路:如果都不能建立码头的话,我们直接调用最小生成树算法就可以了。但是如果有码头的话,我们就要分情况讨论了。
①不用建码头就是连通图。没有说明一定是连通图,因此我们要分情况讨论。这种情况下,我们要看建立码头和不建立码头哪种情况花费最小。
②本身不是连通图。这样我们就只能建立码头了。

我们对于能建立码头的点,设置一个虚节点n+1。建立码头的费用就是到这个虚节点的费用。
对于第一种情况,有可能会有疑问,就是我们按照传统的最小生成树算法去做的话,会出现这一个点建立码头了,但是并没有用到。例如:n=5,我们在4点建立了码头,这样的话,我们在4和6之间建立了一条虚边,假设是代价为1.4和5之间的代价为2,而在5这个点建立码头的代价为100.很显然,我们在跑的时候,会先建立4和6之间的边,在建立4和5之间的边,5和6的边时不会建立的。这样4这里的码头不就白建了吗。
对于这一疑问,我们要在两点间建立码头的前提是,建立码头的总代价,是小于建立公路的代价的。也就是4->6的距离+5->6的距离小于4->5的距离的时候,才会在4和5建立码头。这样的话,4->6的距离和5->6的距离,都小于4->5的距离,这样跑最小生成树才对。这就是为什么我们要取两种情况(建立码头和不建立码头)的最小值。如果出现了上面的情况,建立码头出来的总代价一定是大于不建立码头的总代价的。一个注意的点是:如果这条边是有收益的,无论如何都要建立。

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxx=1e6+100;
const int maxn=1e4+100;
struct node{
	int x,y,v;
	bool operator<(const node &a)const{
		return v<a.v;
	}
}p[maxx];
int f[maxn],val[maxn];
int n,m;

inline void init()
{
	for(int i=1;i<=n+1;i++) f[i]=i;
}
inline int getf(int u)
{
	return u==f[u]?u:f[u]=getf(f[u]);
}
inline void merge(int u,int v)
{
	int t1=getf(u);
	int t2=getf(v);
	if(t1!=t2) f[t1]=t2;
}
inline bool check()//判断联通
{
	init();
	for(int i=1;i<=m;i++) merge(p[i].x,p[i].y);
	int x=getf(1);
	for(int i=1;i<=n;i++) if(getf(i)!=x) return 0;
	return 1;
}
inline int kruskal(int x,int y)
{
	init();
	int sum=0;
	sort(p+1,p+1+y);
	for(int i=1;i<=y;i++)
	{
		int t1=getf(p[i].x);
		int t2=getf(p[i].y);
		if(t1!=t2||p[i].v<0)
		{
			sum+=p[i].v;
			f[t1]=t2;
		}
	}
	return sum;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].v);
	int k=m;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&val[i]);
		if(val[i]!=-1)
		{
			p[++k].x=n+1;
			p[k].y=i;
			p[k].v=val[i];
		}
	}
	if(check()) cout<<min(kruskal(n+1,k),kruskal(n,m));
	else cout<<kruskal(n+1,k)<<endl;
	return 0;
}

努力加油a啊,(o)/~

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

starlet_kiss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值