并查集详解-hdoj1232-国王的烦恼

目录

1.并查集是什么?

2.并查集有什么用?

3.并查集实现思路

4.模版代码(带路径压缩)

5.例题解析

1.HDOJ 1232 并查集模版题

2.蓝桥杯 历届试题 国王的烦恼


1.并查集是什么?

    首先并查集是一种特殊的数据结构,一般表现为一个数组.

2.并查集有什么用?

一般用于解决这种问题:

    在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

标志性经典问题:

    现有n个人,分别表示为p1,p2,....pn;告诉你m对数据a,b,每对a,b表示ab两个人是朋友,在建立朋友关系的同时问你此时c与d两个人是不是朋友.

3.并查集实现思路

    通俗的将并查集就是用一个数组存储每个元素所在集合的上层元素是谁,仅有par[x]==x的元素被认为是根元素,我们会去维护数据使得每个集合中仅会有一个根元素,这个根元素就是这个集合的象征,在需要知道元素a属于哪个集合时,只要不断的向上找,知道找到根元素roota,我们就能知道元素a是属于roota所表示的集合,同样去向上找元素b的根元素rootb,并比敬爱哦roota与rootb是否相同,就能知道ab是否在同一集合,这就是并查集实现思路.

    并查集代码最常见的分为5部分

1.数组par[n]

    par[i],表示i结点的上层结点的下标.

2.负责初始化的init函数

    最开始每个元素自己都是一个只包含自己的小集合,所以每个元素都是根元素,故而对于每个元素i,par[i]=i,完成初始化

3.负责查找元素x所在集合根元素的find函数

    通过判断par[x]是否等于x得到x是不是根元素,如果当前查到的不是根元素,就继续向上递归查找,直到找到根元素为止,返回根元素的同时重置每一层的par[x]直接指向根元素,这就是路径压缩的核心.

4.负责合并元素x所在集合与元素y所在集合的unite函数

    合并x所在集合与y所在集合时直接先找到x的根元素,再找到y的根元素,令par[x]指向y,则x不再是根元素,x和x下属的所有结点的根元素都变成了y,与原先y下属的所有结点拥有同一根元素,合并完成

5.负责查询元素x与元素y是否在同一集合的same函数

    通过比较x的根元素与y的根元素是否相同,即可知道a与b是否在同一集合.

4.模版代码(带路径压缩)

const int max_n=100005;
int par[max_n]; 

void init(int n){  //初始化
    for(int i=1;i<=n;i++) par[i]=i;  
}  
  
int find(int x){  //查找x所在集合的根
    if(par[x]!=x) par[x]=find(par[x]);//递归返回的同时压缩路径
    return par[x];  
}  
  
void unite(int x,int y){  //合并x与y所在集合
    x=find(x);  
    y=find(y);  
    par[x]=y; 
}  
  
bool same(int x,int y){  //x与y在同一集合则返回真
    return find(x)==find(y);  
} 

5.例题解析

1.HDOJ 1232 并查集模版题

Problem Description

某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?

Input

测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。

Output

对每个测试用例,在1行里输出最少还需要建设的道路数目。

Sample Input

 

4 21 34 33 31 21 32 35 21 23 5999 00

Sample Output

 

102998

Huge input, scanf is recommended.

解题思路:使用并查集处理每个城镇的集合关系,x与y之间修建道路即为将x与y所在集合连通,就是unite(x,y),最终计算所有城镇还有多少个集合(即为根节点数目,说白了就是判断par[i]==i的有多少个),剩余num个集合,则最少修建num-1条道路,就可以把n个城镇全部连通

ac代码:

#include <iostream>
#include <stdio.h>
using namespace std;
const int max_n=100005;
int par[max_n]; 


void init(int n){  //初始化
    for(int i=1;i<=n;i++) par[i]=i;  
}  
  
int find(int x){  //查找x所在集合的根
    if(par[x]!=x) par[x]=find(par[x]);//递归返回的同时压缩路径
    return par[x];  
}  
  
void unite(int x,int y){  //合并x与y所在集合
    x=find(x);  
    y=find(y);  
    par[x]=y; 
}  
  
bool same(int x,int y){  //x与y在同一集合则返回真
    return find(x)==find(y);  
} 


int main(){
    int n,m,a,b;
    while(1){
        scanf("%d",&n);
        if(!n) break;
        scanf("%d",&m);
        init(n);
        while(m--){
            scanf("%d%d",&a,&b);
            unite(a,b);
        }
        int num=0;
        for(int i=1;i<=n;i++) if(par[i]==i) num++;
        printf("%d\n",num-1);
    }
    return 0;
}

2.蓝桥杯 历届试题 国王的烦恼

问题描述

  C国由n个小岛组成,为了方便小岛之间联络,C国在小岛间建立了m座大桥,每座大桥连接两座小岛。两个小岛间可能存在多座桥连接。然而,由于海水冲刷,有一些大桥面临着不能使用的危险。

  如果两个小岛间的所有大桥都不能使用,则这两座小岛就不能直接到达了。然而,只要这两座小岛的居民能通过其他的桥或者其他的小岛互相到达,他们就会安然无事。但是,如果前一天两个小岛之间还有方法可以到达,后一天却不能到达了,居民们就会一起抗议。

  现在C国的国王已经知道了每座桥能使用的天数,超过这个天数就不能使用了。现在他想知道居民们会有多少天进行抗议。

输入格式

  输入的第一行包含两个整数n, m,分别表示小岛的个数和桥的数量。
  接下来m行,每行三个整数a, b, t,分别表示该座桥连接a号和b号两个小岛,能使用t天。小岛的编号从1开始递增。

输出格式

  输出一个整数,表示居民们会抗议的天数。

样例输入

4 4
1 2 2
1 3 2
2 3 1
3 4 3

样例输出

2

样例说明

  第一天后2和3之间的桥不能使用,不影响。
  第二天后1和2之间,以及1和3之间的桥不能使用,居民们会抗议。
  第三天后3和4之间的桥不能使用,居民们会抗议。

数据规模和约定

  对于30%的数据,1<=n<=20,1<=m<=100;
  对于50%的数据,1<=n<=500,1<=m<=10000;
  对于100%的数据,1<=n<=10000,1<=m<=100000,1<=a, b<=n, 1<=t<=100000。

解题思路:

    要求的就是居民的抗议天数,其实就是昨天某一对小岛能连通今天不能连通了居民就会抗议,正常思维下问题的难点在于我们是被要求去判断某一天是否存在任意两个桥所有相连的路径都在今天断开了,这样看来看似无法使用并查集

    然而如果我们逆向思维,将桥按使用天数降序排列,然后依次去生成这些桥,生成的时候判断是否原来两小岛原来没有连接,也就是修改unite,x!=y就返回true,如果两小岛原来没有连接现在被我连接上说明今天的后一天居民肯定会抗议,因为后一天没有连接今天有连接,我们倒置了条件,不过并不会导致错误.这里我们还需要小心一个细节就是判断是否重复抗议了,也就是可能存在两对小岛都在同一天符合抗议的条件,然而由于是同一天,答案只用加1,这个细节的处理由于我们是把天数排序好的,故而我们只用保存一个lastconsitday,记录最新的抗议是在哪一天,新的抗议所在的天不能与lastconsitday重复就可以了.

ac代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef pair<int,pair<int,int> > paipaii;
const int maxn=100005;
paipaii brige[maxn];
int par[maxn]; 

void init(int n){  //初始化
    for(int i=1;i<=n;i++) par[i]=i;  
}  
  
int find(int x){  //查找x所在集合的根
    if(par[x]!=x) par[x]=find(par[x]);//递归返回的同时压缩路径
    return par[x];  
}  
  
bool unite(int x,int y){  //合并x与y所在集合
    x=find(x);  
    y=find(y);
    par[x]=y; 
    if(x!=y) return true;
    return false;
} 

bool comp(paipaii a,paipaii b){
	return a.first>b.first;
}

int main(){
	int n,m,a,b,t;
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++) scanf("%d%d%d",&brige[i].second.first,&brige[i].second.second,&brige[i].first);
	sort(brige,brige+m,comp);
	init(n);
	int ans=0,lastconsistday=-1;
	for(int i=0;i<m;i++){
		if(unite(brige[i].second.first,brige[i].second.second)&&brige[i].first!=lastconsistday){
			ans++;
			lastconsistday=brige[i].first;
		}
	}
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_31964727/article/details/80795941