LibreOJ - 10066 新的开始

题目描述

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

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

在这一口矿井上建立一个发电站,费用为 vv(发电站的输出功率可以供给任意多个矿井)。
将这口矿井与另外的已经有电力供应的矿井之间建立电网,费用为 pp。
小 FF 希望身为「NewBe_One」计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费。

输入格式

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

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

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

输出格式

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

样例

Input

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

Output

9

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

思路

两种写法:最小生成树 和 并查集

代码:最小生成树

#include "stdio.h"
#include "string.h"
#include "algorithm"
using namespace std;
#define inf 0X3f3f3f
#define mq(a,b) memset(a,b,sizeof(a))
int dis[9999];
int a[9999][9999],b[9999];
int c[9999];
int main()
{
    
    
	int n,m,j,k=99999,l,i,s=inf;
	mq(a,inf);mq(dis,inf);mq(b,0);
	scanf("%d",&n);
	for(i=1; i<=n; i++)
		a[i][i]=0;
	for(i=1; i<=n; i++)
	{
    
    
		scanf("%d",&b[i]);
		if(s>b[i])
		{
    
    
			s=b[i];
			l=i;
		}
	}
	for(i=1; i<=n; i++)
	{
    
    
		for(j=1; j<=n; j++)
		{
    
    
			scanf("%d",&a[i][j]);
			if(a[i][j]>b[j])//当连网线的成本大于建发电站成本时 
				a[i][j]=b[j];
		}
	}
	for(i=1; i<=n; i++)
		dis[i]=a[l][i];
	c[l]=1;//标记 
	int h=1;
	while(h<n)
	{
    
    
		m=inf;
		for(j=1; j<=n; j++)
		{
    
    
			if(c[j]==0&&m>dis[j])
			{
    
    
				m=dis[j];
				k=j;
			}
		}
		s+=dis[k];
		h++;c[k]=1;
		for(i=1; i<=n; i++)
		{
    
    
			if(c[i]==0&&dis[i]>a[k][i])//因为k点已经有电了,所以看看没有电的点和k点连接会不会更省钱, 
				dis[i]=a[k][i];//不要c[i]==0也可以,写出来呢是为了方便解释
				 
		}
	}
	printf("%d\n",s);
}
		

解释 上面某些步骤

for(i=1; i<=n; i++)
{
if(c[i]==0&&dis[i]>a[k][i])
dis[i]=a[k][i];
}
我来解释一下这段代码,为什么是 dis[i]>a[k][i]而不是 dis[i]>dis[k]+a[k][i];
这有a到b有两条路,电可以从a直接到b,a经过c再到b,但是了:a到b未建网线,a到c已经建好网线,c到b未建网线

	 a 
	| \
    |  \
	|	\ 
	|    c
	|   /
	|  / 
	| /
	b
此时你想要ab通电,你只要比较ab建网线和cb建网线拿个更便宜 

代码:并查集

#include "stdio.h"
#include "string.h"
#include "algorithm"
using namespace std;
int a[999999+10],n;
struct ppp
{
    
    
	int a,b,l;
}p[999999];
void init()
{
    
    
	for(int i=0; i<=n; i++)
		a[i]=i;
}
bool cmp(ppp z,ppp w)
{
    
    
	return z.l<w.l;
}
int find(int z)
{
    
    
	if(a[z]==z) return z;
	return a[z]=find(a[z]);
}
int main()
{
    
    
	int m,j,k=0,i,s=0;
	scanf("%d",&n);
	init();
	for(i=1; i<=n; i++)
	{
    
    
		scanf("%d",&m);
		p[k].a=0,p[k].b=i,p[k++].l=m;//很巧妙,建立发电站点与0相连 
	}//这道题意思是每个点只要有电就行,单看发电站我们可以知道有两个及以上的发电站用并查集写的话
	//你有几个发电站,就会有几个首领(几个集合,不相交),而用了0后就很巧妙,无论你有几个发电站,
	//最后都是一首领(一个集合) 
	for(i=1; i<=n; i++)
	{
    
    
		for(j=1; j<=n; j++)
		{
    
    
			scanf("%d",&m);
			p[k].a=i,p[k].b=j,p[k++].l=m;
		}
	}
	sort(p,p+k,cmp);
	int h=0;
	for(i=0; i<k; i++)
	{
    
    
		if(h==n)
			break;
		int x=find(p[i].a);
		int y=find(p[i].b);
		if(x!=y)
		{
    
    
			a[x]=y;
			h++;
			s+=p[i].l;
		}
	}
	printf("%d\n",s);
}

Guess you like

Origin blog.csdn.net/weixin_53623850/article/details/119256122