并查集

并查集

洛谷der链接

题面

题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式

第一行包含两个整数 \(N,M\) ,表示共有 \(N\) 个元素和 \(M\) 个操作。

接下来 \(M\) 行,每行包含三个整数 \(Z_i,X_i,Y_i\)

\(Z_i=1\) 时,将 \(X_i\)​ 与 \(Y_i\)​ 所在的集合合并。

\(Z_i=2\) 时,输出 \(X_i\)​ 与 \(Y_i\)​ 是否在同一集合内,是的输出 \(Y\) ;否则输出 \(N\)

输出格式

对于每一个 \(Z_i=2\) 的操作,都有一行输出,每行包含一个大写字母,为 \(Y\) 或者 \(N\)

讲解

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。 (摘自百度)

关于并查集和路径压缩:

现在我们假定 \(f[i]\) 表示第 \(i\) 个人的老大是谁。

现在我们有甲,乙,丙三个人(分别用 \(a, b, c\) 表示)

假设甲和乙打架了,甲做了丙的小弟。则有 \(f[a]=b\)

后来甲打赢了丙

那么丙就是甲的小弟了。有 \(f[c]=a\)

但是如果我们这样表示,丙不能直接知道甲,容易自己人打自己人

所以,我们必须直接让丙的大哥变成最大的老大。

定义函数 \(find\)

int find(int k){
    if(f[k]==k)return k;
    return find(f[k]);
}//find 函数可以直接找到最大的老大

f[c]=find(a);
//丙的老大是甲

这时,因为我们要路过他所有的上级,我们也可以顺便使途中经过的人的大哥也变成老大。

//路径压缩
int find(int k){
    if(f[k]==k)return k;
    return f[k]=find(f[k]);
    /* 
        即:
        f[k]=find(f[k]);
        return f[k];
    */
}

f[c]=find(a);

简直是太巧妙了!

而判定两个人的老大是否相等,只需

if(find(a)==find(b))

就好了。

一些设定:

  • 一个人不能有两个老大。
  • 当已经有老大的人臣服时,老大也将成为胜利的人的小弟。

代码:

#include<bits/stdc++.h>
using namespace std;
int i,j,k,n,m,s,ans,f[10010],p1,p2,p3;
//f[i]表示i的集合名
int find(int k){
    //路径压缩
    if(f[k]==k)return k;
    return f[k]=find(f[k]);
}
int main()
{
    cin>>n>>m;
    for(i=1;i<=n;i++)
        f[i]=i;//初始化i的老大为自己
    for(i=1;i<=m;i++){
        cin>>p1>>p2>>p3;
        if(p1==1)
            f[find(p2)]=find(p3);
            //p3打赢了p2
        else
            if(find(p2)==find(p3))
            //是否是一伙的
                printf("Y\n");
            else
                printf("N\n");
    }
    return 0;
}
posted @ 2020-04-26 16:10  SweepyZhou  阅读(144)  评论(0)    收藏  举报