欧几里得生成树 附洛谷1265

你只有非常努力才能看起来毫不费力。

https://www.luogu.org/problemnew/show/P1265

上面的洛谷链接就是一个欧几里得生成树,注意这道题目里面的第二个条件是不成立的!!!

一:什么是欧几里得生成树?(大概是这么个意思吧)

就是给你n个包含x、y轴的坐标,将这些点连成最小生成树,如此生成树就是欧几里得生成树。我们的目的是记录最小生成树上的总的路径和。

二:解题过程

接下来我就写下我自己求欧几里得最小生成树的一些历程:

1:kruskal行不行呢?对于这道题目不可以,因为是5005个点,那么要push进队列5005*5005-1条边,会MLE。开始自己打了一个kruskal就MLE了,难受。

2:然后那毫无疑问了,用prim。很多prim模板里面都是设一点为起点p,建立结构体。结构体里面存储的是一个点的序号以及这个点到源点p的最小距离,然后用优先队列进行维护。

3:但是最后我们要记录最小生成树上的路径和,该怎样处理呢?

  1. 好吧,一切从头开始。我们先从源点出发,将除源点外所有点都遍历一遍,将源点到下一个点j的距离保存在dis[j]里面。
  2. 然后用vis数组将源点标记为1;
  3. 我们再从还未标记(还没有以这个点为起点更新它周围的点的一个点)的点i里面找到dis[i]最小的那个。然后再用这个点更新那些没有被标记的点j。i到j的距离为len,if(len<dis[j]) 就把dis[j]变成len。
  4. 当时我也很迷第3步,为什么要这样做呢?
    我们将我们先遍历到的点称之为父亲,后遍历到的称谓孩子。对于每一个孩子,它在最小生成树上,意味着一定有一条从父亲方向来的边接到它身上并且这条边是是最小生成树的边,而我们的找最小生成树其实就是让这条边尽量的小。我们在1中从源点更新了源点这个父亲目前到所有孩子的距离,但是这个距离对于孩子来说不一定是最优的啊,所以我们要通过更新来让每一个结点和父亲之间的边最小。

4:和一般prim算法的区别:

  1. 一般prim算法是找到源点路径总路径最小的点
  2. 欧几里得生成树求路径,是找和父亲之间距离最小的点。

两者虽然贪心的方式不同,但都是利用了MST性质,都是正确的。 

#include <bits/stdc++.h>
using namespace std;
#define  inf 0x3f3f3f3f
#define ll long long
#define res  register int
const int maxn=5005;
struct Node {
	double x,y;
} node[maxn];
int N,vis[maxn];
double dis[maxn];
void init() {
	cin>>N;
	for(res i=1; i<=N; i++) {
		scanf("%lf%lf",&node[i].x,&node[i].y);
		dis[i]=inf;
	}
}
double cal(int a,int b) {
	return sqrt((double)(node[a].x-node[b].x)*(node[a].x-node[b].x)+(double)(node[a].y-node[b].y)*(node[a].y-node[b].y));
}
int main() {
	init();
	double len=0;
	dis[1]=0;
	for(int i=1; i<=N; i++) {
		int minone,temp=inf;//minone记录的是当前没有被遍历过且dis最小的一个 (由MST性质)
		for(int j=1; j<=N; j++)
			if(!vis[j]&&temp>dis[j]) minone=j,temp=dis[j];
		vis[minone]=1;
		for(int j=1; j<=N; j++) {
			double d=cal(minone,j);
			if(d<dis[j]&&j!=minone&&!vis[j]) dis[j]=d;//发现新的距离小于dis[j] && j不是minone本身 && 并且这个点没有被访问过
		}
	}
	for(int i=1; i<=N; i++)
		len+=dis[i];
	printf("%.2lf",round(len*100)/(double)100);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41755258/article/details/84894980