HLG 1679 跑步 -- 并查集

链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1679


在解题之前我们先了解一下一些关于并查集的一下基本概念和方法吧;

定义:

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。

主要操作:

初始化----把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
查找----查找元素所在的集合,即根节点。
合并----将两个元素所在的集合合并为一个集合。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。

树结构的并查集

 

采用树结构支持并查集的计算能够满足我们的要求。并查集与一般的树结构不同,每个顶点纪录的不是它的子结点,而是将它的父结点记录下来。下面是树结构的并查集的两种运算方式

 

⑴直接在树中查询

⑵边查询边“路径压缩”

 

对应与前面的链式存储结构,树状结构的优势非常明显:编程复杂度低;时间效率高。

 

直接在树中查询

 

集合的合并算法很简单,只要将两棵树的根结点相连即可,这步操作只要O(1)时间复杂度。算法的时间效率取决于集合查找的快慢。而集合的查找效率与树的深度呈线性关系。因此直接查询所需要的时间复杂度平均为O(logN)。但在最坏情况下,树退化成为一条链,使得每一次查询的算法复杂度为O(N)。

 

边查询边“路径压缩

 

其实,我们还能将集合查找的算法复杂度进一步降低:采用“路径压缩”算法。它的想法很简单:在集合的查找过程中顺便将树的深度降低。采用路径压缩后,每一次查询所用的时间复杂度为增长极为缓慢的ackerman函数的反函数——α(x)。对于可以想象到的n,α(n)都是在5之内的。

 

并查集:(union-find sets)是一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多。一般采取树形结构来存储并查集,并利用一个rank数组来存储集合的深度下界,在查找操作时进行路径压缩使后续的查找操作加速。



下面来看一下这道题的代码吧:


#include<iostream>

#include<cstring>

#include<cstdio>

#include<cmath>

#include<algorithm>

using namespace std;

#define clr(x)memset(x,0,sizeof(x))

 

int father[600];

int b[600];

int n, m, i, j, flag;

double res;

 

struct node {

   int x, y;

   double dist;

}e[600*600];

 

int cmp(node a, node b) {  //排序

   return a.dist < b.dist;

}

 

int find(int x) { //查找每个点的父亲节点;

   if(father[x]==x)return x;

   return father[x]=find(father[x]);

}

bool join(int x,int y) { //集合的合并实现

   x=find(x);

   y=find(y);

   if(x==y)return false;

   if(x>y){father[x]=y;b[y]+=b[x];if(b[y]>=m&&flag==0){flag=1;res=e[i].dist;printf("%.4lf\n",res);return0;}}

   if(x<y){father[y]=x;b[x]+=b[y];if(b[x]>=m&&flag==0){flag=1;res=e[i].dist;printf("%.4lf\n",res);return0;}}

   return true;

}

 

int x[600]; //x, y 用来储存每个点的横坐标和纵坐标;

int y[600];

 

int main() {

   int n,T;

   scanf("%d", &T);

   while(T--) {

           flag = 0;

           int tot = 0;

           scanf("%d%d", &n, &m);

           for(i=0; i<n; i++) { //输入坐标

                scanf("%d%d",&x[i], &y[i]);

           }

            for(i=0; i<n; i++) {

                for(j=i+1; j<n; j++) { //将每一点的坐标到除自身以外所有的点的距离;

                    e[tot].x = i;

                    e[tot].y = j;

                    e[tot++].dist =sqrt((double)(x[i]-x[j])*(x[i]-x[j])+(double)(y[i]-y[j])*(y[i]-y[j]));

                }

           }

           for(i=0; i<=n; i++) { // 初始化

                father[i] = i;

                b[i] = 1;

           }

           sort(e, e+tot, cmp); //对路程进行排序

           for(i=0; i<tot; i++) {

                join(e[i].x,e[i].y); //集合的合并

                if(flag == 1) break;

           }

    }

   return 0;

}

欢迎交流;


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值