88888888y

导航

 

题目:http://ybt.ssoier.cn:8088/problem_show.php?pid=1487

题目思路:很明显的最小生成树

最小生成树,一般在实际生活中用于解决修建铁路或是服务器链接的问题

具体就是给你 n 个点,让你把这 n 个点拼起来,使其两两相通

但是吧,每连起来两个点都会花钱,最小生成树就是为了找到钱最少的一种连法

在这种最小生成树上,衍生出了一种叫 “MST” 的东西,一个算法,以及一个定理

MST性质:假设N = (V,{ E })是一个连通网,U 是顶点集V的一个非空子集。若(u , v )是一条具有最小权值(代价)的边,其中u∈U, v∈V - U,则必存在一棵包含边(u,v)的最小生成树。

看不懂没关系,它通俗来讲,就是把所有顶点分成两块,这两块里面各取一个顶点,如果这俩货连起来要花的代价相对于其他的,让这两块顶点区域相通的边最小,那么我们最终的成品,肯定有这条边在里面

那么,依照这个性质,我们又想到了“并查集”这个算法

最开始,每一个顶点我们就看成一个块,叫做“单元素集合”,什么 U,U1,U2......U8......U10086全都整上

然后,按照“最小”的字眼挑两个幸运的 U ,让他们作为一个大块里的 U 和 V 

前面说好的“必存在一棵包含边(u,v)的最小生成树”,那么接下来,U,V凑起来之后,他们就成了新的块,暂且命名 U10087

然后,继续按照“最小”的字眼,一个一个的小块融合成大块,最终由多变一,树来!

而那个定理呢?试想一下有 3 个点构成了一个环,咱能不能删去一条边?可以

如果是 n 个点被拼成了一个类似于圆的玩意呢?我们也能删去一条边。

所以说,把 n 个点拼在一起,至多需要 n-1 条边

即使不按照”环“的方式去连,等到过了 n-1,就连最优解都不是了。

所以,"把 n 个点拼在一起,最多只需要 n-1 条边",

更完整的,运用反证法,我们可以得知,"把 n 个点拼在一起,一定要 n-1 条边,且只要 n-1 条边"

这对于一整个大顶点集中的一小部分也同样适用

换句话说,在最小生成树里不可能出现闭环

现在,看到“最小”那个字眼了吗,尘封多年的“贪心”又可以用了

在"MST"和“贪心”的共同作用下,衍生出了一种算法:kruskal

首先是一大堆的顶点,这是我们做题的先提条件

然后,或是利用坐标求或是人家给,把顶点与顶点之间连接的代价搞出来

用结构体存下来对应的点和边,按照边的大小排排坐,小的在前面

然后就可以开干了

首先,我们需要找一个数组 f,f[i]=j,代表顶点 i 和 j 被放在了一个块里

而我们又能干出 f[j]=k 的玩意,这样就把两个,亦或是 n 个点(f[k]=p,f[p]=g......)算作一个集

最开始的时候,f[i] 都是 i,代表一个顶点一个集,这是“并查集”算法应用的伊始

接下来,引入那个结构体数组,把最前面那个提搂出来

因为“边的大小排排坐,小的在前面”,就能保证它是“要花的代价相对于其他的,让这两块顶点区域相通的边最小”,那么第一个 U 和 V 成了一个大块

接下来如法炮制,由多变一,最小生成树就这么搞定了..................吗?

别忘了还有一种问题,“在最小生成树里不可能出现闭环”,所以有的时候就算小,也要跳过,不然就炸了

怎么看?还记得数组 f 吗

从 f[i] 开始找到 j,再找到 k,再往后,如果从这将要连接的两个点跑着跑着跑到了同一个地方

也就是说,他们已经是一个集里的蚂蚱了,再连直接闭环,原地光速去世

continue continue,赶紧 continue

 如果能连上,就从0开始计数

依照“把 n 个点拼在一起,一定要 n-1 条边,且只要 n-1 条边”,等什么时候到地方了,记录,退出,好~

现在,终于可以回到我们的问题了

由于有 k 个卫星可以帮我们“精准打击”,那最小生成树里,前 k 大的边可以直接用卫星连,就不花那冤枉钱买无线电了

因此,记录到 n-k 的时候就可以记录加退出了

然后由于人家要最贵的一台无限电,我们又是从小到大记录的,到地方直接把最后一个我们搞出来的记下来就好

下面是完整代码:

 

#include<bits/stdc++.h>
using namespace std;
int n,k,m=1,f[501],x[501],y[501];
//x和y是点坐标,f是“集标识” 
double d;
//d是将来的最终答案 
struct jardin{
	int s,e;
	double di;
}q[400001];//s,e是这条边的两个端点 
//di是这条边的权值(要花的钱) 
double dis(int xs,int xe,int ys,int ye){
	return sqrt((xs-xe)*(xs-xe)+(ys-ye)*(ys-ye));
}//用坐标计算这条边的权值
bool cmp(jardin a,jardin b){
	return a.di<b.di; 
}//小的在前面 
int find(int t){//找起点 
	if(f[t]==t){//如果等于
	//说明这里就是这个点走完路径后的最终点 
		return t;//打回 
	}
	f[t]=find(f[t]);
	//不然就是接着往下找 
	return f[t];
}
void kruskal(){//核心 
	int f1,f2,side=0;
	//两个点的最开始的点
	//side计数 
	for(int i=1;i<=n;++i){
		f[i]=i;
		//最开始是一个点一个集 
	}
	for(int i=1;i<=m;++i){
		//一直到第m个边 
		f1=find(q[i].s);
		f2=find(q[i].e);
		//看起点 
		if(f1!=f2){
			//不等于,说明不会出现闭环 
			f[f1]=f2;
			//连起来当一个集 
			side++;//计数 
			if(side==n-k){
				//到地方了 
				d=q[i].di;//计数 
				break;//再见 
			}		
		}
	}
}
int main(){
	scanf("%d%d",&n,&k);
	//输入 
	if(k>=n){
	//村均一台卫星 
	    printf("0.00");
	    //还花个屁钱 
	    return 0;
	}
	if(k==0){
		k=1;
		//就算是一个卫星都没有
		//算到 n-1 也就够了 
	}
	for(int i=1;i<=n;++i){
		scanf("%d%d",&x[i],&y[i]);
		//点坐标打进去 
	}
	for(int i=1;i<=n;++i){
		//从第一个点开始遍历 
		for(int j=i+1;j<=n;++j){
			//它不能和自己以及它已经连过的点相连 
			q[m].s=i;//起点 
			q[m].e=j;//终点 
			q[m].di=dis(x[i],x[j],y[i],y[j]);
			//连线 
			m++;//记数字 
		}
	}
	sort(q+1,q+m,cmp);
	//前面在最后一次循环里已经加过一了
	//这里不用多加 
	kruskal();
	printf("%.2lf",d);
	//两位小数 
	return 0;
}

 

posted on 2022-06-19 10:52  88888888y  阅读(113)  评论(0)    收藏  举报