模板:并查集

(好的期末考试考砸的我滚回来写代码来了TAT)
(咦我什么时候期末考试考好过~~)
却说勇者有很多的亲戚,亲戚又有很多的亲戚,亲戚的亲戚就是自己的亲戚。
不幸的是,亲戚中有些人不是很有钱,所以说会管勇者借钱。
勇者丝毫不介意啊,毕竟自从他讨伐完大龙之后,他就日日有国王一个金币的俸禄。
可是,终有一天,勇者发现一天之内,亲戚就来了十多个。
还是路由器发现的,他将族谱整理完之后才告诉他说:“那些人有一半都不是你们家族的,就是过来诈骗的!”
勇者哑口无言,为了拯救自己的金币,他需要知道哪些人是自己的亲戚。

——————————————

输入格式:
第一行包含两个整数N、M,表示共有N个人和M次操作。
接下来M行,每行包含三个整数Zi、Xi、Yi
当Zi=1时,表示Xi与Yi是亲戚
当Zi=2时,输出Xi与Yi是否是亲戚,是的话输出Y;否则话输出N
输出格式:
如上,对于每一个Zi=2的操作,都有一行输出,每行包含一个大写字母,为Y或者N
提示:
亲戚的亲戚是亲戚

——————————————

(题外话)
好的,我们接下来就是要讲一个高深莫测玄幻之际的——并查集。
好吧在路由器没学之前一直以为并查集是huge佬所学的。
那么今天将带给蒟蒻也能看懂的并查集教程!
首先让我们知道并查集的作用:简短点说,就是需要完成的任务有1.合并集合2.查询两个元素是否在同一个集合内。
上面那句话一定要看懂再往下看(不然你会和路由器一样当机半小时)
那么,让我们正式开始。

——————————————

勇者花费了一天的时间,终于找齐了所有七大姑八大姨了,那么接下来就是需要靠古老魔法的帮助来完成族谱了。
路由器也在照相馆中找到了魔法书,上面赫然写着三个字。
并查集
“并查集的实现是类似于树一般,一整棵树代表了一整个集合。这样做的好处是,我们要想知道元素是否在同一个集合内,只需要看他们所在的集合的根节点是否一样即可。”
“但是,这并非意味着图论,因为链式前向星有很大的空间限制。”
“我们用一个很简单的方法实现建树——fa数组(指father,不要想象成奇怪的东西)”
“fa数组的作用,fa[i]=j表示j是i的爸爸。规定根节点的爸爸为根节点。”
“这样,当我们知道了要合并的两个元素i,j时,找到i与j的根节点if,jf,我们就可以简单粗暴的用fa[if]= jf来实现。”
“那么就只有一个问题了,查询。”
“查询方法:即不断递归,就是fa套fa套fa套fa……直到fa[i]=i,这时候我们就找到了所在集合的根节点了。”
“然后比较这样得到的根节点即可,相等,即为一个集合……”
勇者“啪”的一声跑了出去,然后用自己的魔法代码功力敲完了魔法。
(但是这个魔法有缺陷,因此不贴出啦!实际上是懒~)

然而有一天,勇者气喘吁吁的找到了路由器。
“路由器,那个魔法……他,不管用了!”
“啊?怎么回事?”路由器疑惑问道。
“额……我把这个魔法卖给了精灵族,但他们第二天就要求退货,说不灵了。”
“不灵到不至于,怕不是太慢。”路由器懒洋洋地说,“那到底怎么回事啊?”
“因为精灵能活千多岁……所以,”勇者支吾道,“他们将自己输入,再把爸爸输入,再爷爷,再太爷爷,再太太爷爷,再太太太爷爷,再……”
“行了,我知道什么问题了,”路由器拜拜手,“你还是把书好好看看吧!”
勇者拿起书,将最后一点问题看完。
“……然而,有的时候,这样做出来的树实际上只有几个支链,有的甚至没有支链,那么找的速度就会明显增加。”
“但是解决的办法也很简单——我们先查一遍,然后把所有沿途遇到的元素的爸爸(fa)全设为根节点即可。”
“以下是本书附带魔法代码,如有不懂参看注释。”

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int fa[10001]={0};
int find(int a){//找到a所在的集合的根节点 
    if(fa[a]!=a)fa[a]=find(fa[a]);
    //路径压缩优化
    //当我们找到根节点的时候
    //我们将这一路上我们往上爬的所有的节点
    //全部指向这个根节点,这样再搜就会很快 
    return fa[a];
    //最开始的时候,我们碰到了第一个fa[a]==a
    //此时我们就知道了fa[a]为根节点
    //将fa[a]的值传递给上一个fa[a],完成路径压缩 
    //经过压缩之后到这个a,此时fa[a]就是根节点 
}
void judge(int a,int b){//判断是否在同一个集合里 
    if(find(a)!=find(b)){
        printf("N\n");
    }else{
        printf("Y\n");
    }
    return;
}
void unionn(int a,int b){//合并以a,b为*根节点*与的两个集合
    fa[b]=a;//简单粗暴的将两棵树连接起来 
    return;
}
int main(){
    int n,m;//N个元素和M个操作
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        fa[i]=i;
        //初始化,将每一个集合视为一棵树
        //则开始时对于每一个元素,他的根节点就是他本身 
    }
    for(int i=1;i<=m;i++){
        int a1,b1,a,b,z;
        scanf("%d%d%d",&z,&a,&b);
        if(z==1){
            a1=find(a);
            b1=find(b);
            if(a1!=b1){//不是同一个集合 
                unionn(a1,b1);
            }
        }else{
            judge(a,b); 
        }
    }
    return 0;
}

下面附带模板题:
洛谷 P3367 【模板】并查集:https://www.luogu.org/problem/show?pid=3367#sub
真的是模板,你只需要将上面的程序复制粘贴就AC啦!

 

posted @ 2017-07-14 18:54  luyouqi233  阅读(110)  评论(0编辑  收藏  举报