并查集模板和例题

——————————来自南昌理工学院ACM集训队

原理(参考大佬思路)

就比如XX国有许许多多个大侠,各自在国内游荡,每个大侠在国内都有自己的朋友,大侠们特别讲义气,就是绝对不对朋友和朋友的朋友出手,一直按照“朋友的朋友就是我的朋友”的原则,但是呢,每个大侠压根不认识自己朋友的朋友,见到别的大侠都不太敢直接动手。所以呢,我们想了一个办法,就是创造一个集体把所有有这种关系的人都聚集起来,这样不是同一个集体的人就可以相互大大出手了,之后我们在集体中推举出一个人当队长来管理这个集体,这样只要区分是不是相同的集体就可以知道是不是朋友了。
但是呢,大侠们只认识自己直接的朋友,根本不知道自己所在集体的队长是谁,要找到队长那得一个一个的去问:“你是不是队长?”,这样太过于麻烦并且没有方向。于是我们可以把集体的成员分为阶层来,分为总队长下面连接着二阶队长,二阶下面连接着三阶队长,以此类推。每个大侠只要记住自己的上级是谁就行,两位大侠相遇时,只需要直接往自己上找就可以短时间确定两人的总队长是否相同来确定是不是朋友了,这样所谓的门派就产生了。
接下来就是实现过程:
int a[1000]
数组a[1000]用来存每一位大侠的上级,成员的编号是0,1,2,3…n(根据题意),例如a[20]=5,说明20号大侠的上级就是5号大侠,如果大侠的上级就是他自己的话说明他就是这个集体的总队长,找到总队长之后,查找也就结束了。

void csh(int x) {
 for (int i = 1; i <= x; i++) {//初始化:每个大侠的上级都定义为自己
  a[i] = i;
 }
}

初始化之后就是查找了:
如果每次都是从底下往上找的话,每次的复杂度就是树的深度,这样深度高起来的时候处理起来也会很麻烦,所以我们这里有一种优化的方法——路劲压缩:
建立集体的过程就是把每个大侠连在他的上级下面,这样有可能会产生成一字长列,这样查找起来也很不方便,最理想的状态就是所有大侠的的上级就是集体的总队长,这样深度就保持在两级,这样查找上级的时候也方便得多。
例如,大侠1和大侠2在路上遇见,想知道是不是可以开打,就相互问对方的上级,1号大侠打电话给自己的上级:“你是不是总队长?”,1号大侠的上级说:“不是啊,我的上级是5号大侠。”之后又打电话给5号大侠,以此往复,最后1号大侠和2号大侠知道自己的总队长都是8号大侠,两个人就非常高兴,但是呢,还有一件事,就是路径压缩,于是1号大侠和2号大侠都直接拜在了8号大侠下,这样他们的上级就直接是集体的总队长了。
就这样一直查找,然后进行路径压缩,让整个数的深度变低。
下面这个get函数是用来查找总队长

int get(int x) {
    if (a[x] == x) { // x号大侠的上级就是自己,说明x号大侠是总队长
        return x; 
    }
    return a[x] = get(a[x]);
}

接下来让我们来看看这个merge函数:
直接举个例子来解释吧,就是比如4号大侠的总队长是8号大侠,5号大侠的总队长是9号大侠,两个人碰上了,就想要一教高下,但是呢,我们这个社会是要求和平的,于是就要求他们做朋友,那我们是不是要把9号大侠的成员一个一个连接在8号大侠的下面呢,并不用,直接把9号大侠连在8号大侠的下面就可以了,9号大侠肯定不服气,但是我们不管。

void merge(int x, int y) {
    x = get(x);
    y = get(y);
    if (x != y) { 
        a[y] = x;
    }
}

例题

原题

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int a[5005],b[5005];
int read() {//快读,也可以用cin输入
 long long x = 0, f = 1; char c = getchar();
 while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
 while (c >= '0' && c <= '9') { x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); }
 return x * f;
}
void csh(int x) {
 for (int i = 1; i <= x; i++) {
  a[i] = i;
 }
}
int get(int x) {
    if (a[x] == x) {
        return x; 
    }
    return a[x] = get(a[x]);
}
void merge(int x, int y) {
    x = get(x);
    y = get(y);
    if (x != y) {
        a[y] = x;
    }
}
int main()
{
 memset(b, 0, sizeof(b));
 int n=read(), m=read(), p=read();
 csh(n);
 for (int i = 1; i <= m; i++) {
     int x = read(), y = read();
     merge(x,y);
 }
 for (int i = 1; i <= p; i++) {
      int a1 = read(), a2 = read();
      a1 = get(a1);
      a2 = get(a2);
      if (a1 == a2) {
         b[i] = 1;
      }
}
 for (int i = 1; i <= p; i++) {
     if (b[i]) {
        cout << "Yes" << endl;
     }
     else {
        cout << "No" << endl;
     }
 }
 return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值