C - 新的开始

先来看看题目呀,应该看过好几遍了叭:

题目描述

发展采矿业当然首先得有矿井,小 FF 花了上次探险获得的千分之一的财富请人在岛上挖了 nn 口矿井,但他似乎忘记考虑的矿井供电问题……

为了保证电力的供应,小 FF 想到了两种办法:

  1. 在这一口矿井上建立一个发电站,费用为 vv(发电站的输出功率可以供给任意多个矿井)。
  2. 将这口矿井与另外的已经有电力供应的矿井之间建立电网,费用为 pp。

小 FF 希望身为「NewBe_One」计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费。

输入格式

第一行一个整数 nn,表示矿井总数。

第 2\sim n+12∼n+1 行,每行一个整数,第 ii 个数 v_ivi​ 表示在第 ii 口矿井上建立发电站的费用。

接下来为一个 n\times nn×n 的矩阵 pp,其中 p_{i,j}pi,j​ 表示在第 ii 口矿井和第 jj 口矿井之间建立电网的费用(数据保证有 p_{i,j}=p_{j,i}pi,j​=pj,i​,且 p_{i,i}=0pi,i​=0)。

输出格式

输出仅一个整数,表示让所有矿井获得充足电能的最小花费。

样例

InputOutput
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
9

小 FF 可以选择在 44 号矿井建立发电站然后把所有矿井都不其建立电网,总花费是 3+2+2+2=93+2+2+2=9。

数据范围与提示

对于 30\%30% 的数据:1\le n\le501≤n≤50;
对于 100\%100% 的数据:1\le n\le 300,0\le v_i, p_{i,j}\le 10^51≤n≤300,0≤vi​,pi,j​≤105。

题意:把发电站当作供电矿井,把需要用电的,看成用电矿井,就是求如何把供电矿井和用电矿井全部连起来最小,连成一颗最小生成树。

这题我用kruskal算法,卡在了80分,就不细讲啦,贴一下码:

都属于最小生成树问题嘛!!!

我觉得最小生成树问题的核心在于建图。

比如这题,使用kruskal算法,我们如何建图呢:

1.已经存在的电站的点,我们把它的信息存进结构体的三个元素里,kk[i].x,kk[i].y,表示点,kk[i].t表示权边。

这里为什么是n+1呢:因为存下面n*n个费用的时候,我们要使用到n了,可以将它看成第n+1个点,自己连自己的。

由于我们要把单独的点和下面的n*n的费用存进一张图,所以我们单独开了个k当作kk数组的序列,记录kk数组的总数。

kruskal算法的具体就不讲啦,就是并查集+排序,掌握的不好的看这个博客:算法打卡第二天之——修复公路(并查集+Kruskal算法)_ly14158的博客-CSDN博客哟西!又到了晚间写题解的时间啦!!!今天呢,还是和昨天一样是并查集的算法呀!!!但是奥,小小的不一样,因为我们今天的题目还要用到Krukal算法,简单来说就是并查集的升级版题目呀!!!Emmmmm...开始之前,按照惯例,我们来稍(xue)微地讲一下,什么是Krucal算法。Krukal算法克鲁斯卡尔算法是求连通网的最小生成树的另一种方法。与普里姆算法(Prim)不同,它的时间复杂度为O(eloge)(e为网中的边数),所以,适合于求边稀疏的网的最小生成树。克鲁斯卡尔(Kruskal)算法从另一途https://blog.csdn.net/ly14158/article/details/122385749?spm=1001.2014.3001.5501

讲的是非常非常仔细啦!!!

#include<bits/stdc++.h>
using namespace std;
long long int n,k,i,j,m,sum=0,a[1010101],b[1010101],f[1010101],cnt=0;
struct node{
	long long int x,y,t;
}kk[10100101];
bool cmp(node a,node b){
	return a.t<b.t;
}
int find(long long int x){
	if(f[x]==x){
		return x;
	}else{
		return f[x]=find(f[x]);
	}
}
int main(){
	cin>>n;
	for(i=1;i<=n;i++){
		f[i]=i;
	}
	for(i=1;i<=n;i++){//建图过程!!! 
		cin>>a[i];
		kk[k].x=i;
		kk[k].y=n+1;
		kk[k].t=a[i];
		k++;
	}for(i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			cin>>m;
			kk[k].x=i;
			kk[k].y=j;
			kk[k].t=m;
			k++;
		}
	}sort(kk+1,kk+k,cmp);
	for(i=1;i<=k-1;i++){
	long long int f1=find(kk[i].x);
	long long int f2=find(kk[i].y);
		if(f1!=f2){
		f[f2]=f1;
		sum+=kk[i].t;}	
	}
	cout<<sum<<endl;
}

主要想讲讲prim算法,(看懵了下午,看到了一位巨巨写的很优雅很优雅的prim):

#include<bits/stdc++.h>
using namespace std;
int i,j,k,n,ans,a[310][310],d[310],vis[310];
int main(){
	memset(d,0x3f,sizeof(d));//初始化成最大 
	a[n+1][n+1]=0,d[1]=0;
	cin>>n;
	for(i=1;i<=n;i++){
		cin>>a[i][n+1];
		a[n+1][i]=a[i][n+1];
	}for(i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			cin>>a[i][j];
			if(a[i][j]==0){
				a[i][j]=d[0];
			}
		}
	}
	for(i=1;i<=n+1;i++){
		k=0;
		for(j=1;j<=n+1;j++){
			if(d[j]<d[k]&&!vis[j]){
				k=j;
			}
		}vis[k]=1;ans+=d[k];
		for(j=1;j<=n+1;j++){
			if(a[k][j]<d[j]&&!vis[j]){
				d[j]=a[k][j];
			}
		}
	}cout<<ans<<endl;
}

先讲讲啥是prim算法叭:

就是,一个图,不是有一堆点吗?我们随便找一个点,将它当作起点,然后把与它相邻的点的权值都给它标上!!!并找到权值最小的点(贪心的体现!!!找到了就代表访问了,下次就不找他了)再以刚才说的,找到的那个点为起点,重复上述过程!

是不是和dijisktra算法很像!!!???

只能说,确实,理解的dijiskra的话,会更好理解它。

这个码我自己老老实实敲了三遍。

一些小问题:

假设初始全不连通。

看完就知道啦!

存图过程, ,因为这两个点是一样的,所以一起存了,且当作第n+1个点。

 然后下面那个,a[i][j]==0,说明它是对角线的点,自己和自己,不连通,d[0]默认初始化是无穷大(不连通)

这个循环注意,跑的是n+1昂! 因为咱前面存了n+1的打算建立的水电站。

 

这个呢,就是找从一个起点开始,与它所有相邻的点里面,权值最小的那个点,这里就体现了刚开始d[1]=0的作用了,使这个if能跑起来 ,不然所有初始权值都是0x3f,就没有比较的意义了,找到最小的那个,我们就标记那个点已经找过,然后ans+=权值。

比较的权值d[j]都是当前未标记的权值中最小的那个值(由下一个for循环实现,也是贪心的体现) 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值