一种查找祖先的方法——并查集

我们先来看这么一个问题:
第一行包含两个整数\(N、M\),表示共有\(N\)个元素和\(M\)个操作。
接下来\(M\)行,每行包含三个整数\(Zi、Xi、Yi\)
\(Zi=1\)时,将\(Xi\)\(Yi\)所在的集合合并
\(Zi=2\)时,输出\(Xi\)\(Yi\)是否在同一集合内,是的话输出\(Y\);否则话输出\(N\) 传送门(luogu)3397
对于这种问题,我们首先想到的是暴力也许是我太弱了
但是,我们容易发现,由于这题的数据范围,暴力显然是不被接受的,于是,我们需要引入一个新的算法——并查集
再讲这道题之前,我们先引一个比较有名的例子:
有a,b,c三个人
假设a和b打架了,a做了b的小弟。则令f[a]=b;
后来a打赢了c 黑社会
那么c就是a的小弟了。所以,令f[c]=a;
但是,c不知道b,这不符合要求。
所以,我们需要让c知道a,于是我们就定义一个\(find\)函数:

int find(int k) {
 if(f[k]==k) return k;
else return find(f[k]);
}

于是,我们就能写出这么一个代码

#include<iostream>
#include<cstdio>
using namespace std;
int a[10003];
int n,m;
int tot,x,y;
int read() {
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {
        if(ch=='-') f=-f;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int find(int k) {
    if(a[k]==k) return k;
    else return find(a[k]);
}
int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++) {
        a[i]=i;//现将自己定为自己的老大
    }
    for(int i=1;i<=m;i++) {
        tot=read(),x=read(),y=read();
        if(tot==1) {
            a[find(x)]=a[y];//将自己的老大变成$y$的老大(x赢了y)
        }
        else {//判断
            if(find(x)==find(y)) cout<<"Y"<<endl;
            else cout<<"N"<<endl;
        }
    }
}

但是,这会出现问题,为什么?
我们需要加入这么一个式:a[k]=find(a[k])(我们要重新赋值a[k],不然除非\(find\)函数赋值,f数组不变

posted @ 2019-04-19 16:42  zeroqq  阅读(800)  评论(0)    收藏  举报
Live2D