题目: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;
}
浙公网安备 33010602011771号