• 并查集:学习总结


    $ $学习总结:并查集

    蒟蒻的第一篇博客,如有bug,请大佬提出,勿喷。

    并查集:##

    并查集虽说是集合,不过个人觉得类似树形结构,像森林,刚开始每一个节点是一个森林,不断把森林合并,形成树。

    • 是一组不相交的集合。即集合之间没有公共元素,它们的交集是空集。
    • 因此没有“求集合的交集”和“求集合的差集” 操作。
    • 至于“求集合的并集”运算,则只需简单地将两个集合的元素合并在一起就行了,不用考虑剔除重复元素的问题。


    上模板:

    【 P3367 【模板】并查集】

    题目描述

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

    输入输出格式

    输入格式:

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

    接下来M行,每行包含三个整数Zi、Xi、Yi

    当Zi=1时,将Xi与Yi所在的集合合并

    当Zi=2时,输出Xi与Yi是否在同一集合内,是的话输出Y;否则话输出N

    输出格式:

    如上,对于每一个Zi=2的操作,都有一行输出,每行包含一个大写字母,为Y或者N

    代码:

    #include<iostream>
    using namespace std;
    int father[100000+10];
    void init(){						//初始化
        for(int i=0;i<10000+10;i++)  father[i]=i;	//爸爸就是自己
    }
    int find(int x){					//找祖先
        if(father[x]==x)  return x;		//如果爸爸是自己的话,那么他就是这整个集合的root,及祖先。
        else  return father[x]=find(father[x]);	//否则继续向上找
    }
    void findans(int x,int y){			//判两数是否是否在同一集合(并查集)
        int i=find(x);					//找祖先
        int j=find(y);					//同上
        if(i==j)  cout<<'Y'<<endl;		//如果祖先相同,及在同一并查集
        else
        cout<<'N'<<endl;
    }
    void union_(int x,int y){			//合并
        int i=find(x);
        int j=find(y);
        if(i!=j)  father[i]=j;			//将一个数的父亲指向另一个数
    }
    int main(){
        int n,m,p;
        cin>>n>>m;
        init();
        for(int i=1;i<=m;i++){
            int e,a,b;
            cin>>e>>a>>b;
            if(e==1)					//操作:合并
            union_(a,b);
            else						//操作:询问
            findans(a,b);
        }
        return 0;
    }
    

    带秩并查集:

    什么是秩:秩就是并查集的节点数。

    经典题:【打击犯罪】

    某个地区有n(n≤1000)个犯罪团伙,当地警方按照他们的危险程度由高到低给他们编号为1-n,他们有些团伙之间有直接联系,但是任意两个团伙都可以通过直接或间接的方式联系,这样这里就形成了一个庞大的犯罪集团,犯罪集团的危险程度由集团内的犯罪团伙数量唯一确定,而与单个犯罪团伙的危险程度无关(该犯罪集团的危险程度为n)。现在当地警方希望花尽量少的时间(即打击掉尽量少的团伙),使得庞大的犯罪集团分离成若干个较小的集团,并且他们中最大的一个的危险程度不超过n/2。为达到最好的效果,他们将按顺序打击掉编号1到k的犯罪团伙,请编程求出k的最小值。

    【输入】
    第一行一个正整数n。接下来的n行每行有若干个正整数,第一个整数表示该行除第一个外还有多少个整数,若第i行存在正整数k,表示i,k两个团伙可以直接联系。

    【输出】
    一个正整数,为k的最小值。

    【输入样例】
    7
    2 2 5
    3 1 3 4
    2 2 4
    2 2 3
    3 1 6 7
    2 5 7
    2 5 6

    【输出样例】
    1

    【思路】:审题,题目要求,按顺序打击1~k的罪犯团伙,也就是说,要打k号犯罪团伙,必须先打掉1~k-1号犯罪团伙。所以,我们可以从后往前建并查集,一旦发现并查集的秩超过了题目要求(n/2)那么直接输出结果(当前操作的编号)。

    【代码】

    #include<iostream>
    #include<cmath>
    #include<cstdlib>
    #include<cstdio>
    using namespace std;
    int father[100000+10],num[100000+10],dis[100000+10],ans,k,o,e[1000001],a[1001][1001];
    void init()		//初始化
    {
        for(int i=0;i<50000+10;i++){father[i]=i;num[i]=1;}
    }
    int find(int x)	//找祖先
    {
        if(x!=father[x])
        {
            father[x]=find(father[x]);
        }
        return father[x];
    }
    void merge(int x,int y)
    {
        int a1=find(x),a2=find(y);
        if(a1!=a2)
        {
            father[a1]=a2;
            num[a2]+=num[a1];		//秩累加
            if(num[a2]>k){cout<<o<<endl;exit(0);}	//判断是否满足题意
            //if(num[a2]>=k){cout<<o<<endl;exit(0);}
    //        num[a1]=num[a2];
        }
    }
    int main()
    {
        int n,m,p;
        cin>>m;
        k=m/2;
        init();
        for(int i=1;i<=m;i++)
        {
            cin>>e[i];
            for(int j=1;j<=e[i];j++)
            {
            	cin>>a[i][j];
           	}
        }
        for(int i=m;i>=1;i--)
        {
        	o=i;
        	for(int j=1;j<=e[i];j++)
        	{
        		if(i<a[i][j])	//如果(i>a[i][j])  a[i][j]已经被打掉了,所以没用
        		merge(i,a[i][j]);	//建立并查集
        	}
        }
        return 0;
    }
    

    带权并查集&&n倍空间

    什么是权:权就是权值……

    经典题:【食物链】&&【银河英雄传说】

    P1196 [NOI2002]银河英雄传说

    题目描述

    公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。

    宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。

    杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成30000列,每列依次编号为1, 2, …, 30000。之后,他把自己的战舰也依次编号为1, 2, …, 30000,让第i号战舰处于第i列(i = 1, 2, …, 30000),形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为M i j,含义为让第i号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第j号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。

    然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。

    在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利的第i号战舰与第j号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。

    作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。

    最终的决战已经展开,银河的历史又翻过了一页……

    【样例输入】 
    4 
    M 2 3 
    C 1 2 
    M 2 4 
    C 4 2 
    
    【样例输出】 
    -1 
    1
    

    说明

    【样例说明】

    战舰位置图:表格中阿拉伯数字表示战舰编号

    【思路】

    在建并查集的过程中储存两战舰的距离,找答案的时候加以处理。

    【代码】

    #include<iostream>
    #include<cmath>
    #include<cstdio>
    using namespace std;
    int father[100000+10],num[100000+10],dis[100000+10],ans;
    void init()			//初始化
    {
        for(int i=0;i<50000+10;i++){father[i]=i;num[i]=1;}
    }
    int find(int x)		//找祖先
    {
        if(x!=father[x])
        {
            int k=father[x];
            father[x]=find(father[x]);
            dis[x]+=dis[k];	//累加两战舰的距离
            num[x]=num[father[x]];	//加权
        }
        return father[x];
    }
    int findans(int a,int b)		//找答案
    {
        int r1=find(a),r2=find(b);
        if(r1!=r2)
        {
        	return -1;
        }
        else{
            return abs(dis[a]-dis[b])-1;
        }
    }
    void merge(int x,int y)	//模板
    {
        int a1=find(x),a2=find(y);
        if(a1!=a2)
        {
            father[a1]=a2;
            dis[a1]=dis[a2]+num[a2];
            num[a2]+=num[a1];	//权
            num[a1]=num[a2];
        }
    }
    int main(){
        int n,m,p;
        cin>>m;
        init();
        for(int i=1;i<=m;i++)
        {
            int a,b;
            char e;
            cin>>e>>a>>b;
            if(e=='M')
            merge(a,b);	//建并查集
            else
            {
                ans=findans(a,b);
                printf("%d
    ",ans);
            }
        }
        return 0;
    }
    

    n倍空间并查集

    【团伙】此题是双倍空间,【食物链】是三倍空间,当然【食物链】也可以用带权并查集来做。

    【食物链】

    描述

    动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。

    现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。

    有人用两种说法对这N个动物所构成的食物链关系进行描述:

    第一种说法是"1 X Y",表示X和Y是同类。

    第二种说法是"2 X Y",表示X吃Y。

    此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

    1) 当前的话与前面的某些真的话冲突,就是假话;

    2) 当前的话中X或Y比N大,就是假话;

    3) 当前的话表示X吃X,就是假话。

    你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。

    输入

    第一行是两个整数N和K,以一个空格分隔。

    以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。

    若D=1,则表示X和Y是同类。

    若D=2,则表示X吃Y。

    输出

    只有一个整数,表示假话的数目。

    样例输入 
    100 7 
    1 101 1 
    2 1 2 
    2 2 3 
    2 3 3 
    1 1 3 
    2 3 1 
    1 5 5 
    样例输出 
    3
    

    【思路】
    开三倍空间,指的是把数组开三倍空间,分别存同类,猎物,天敌。然后把每一次输入的数据进行判断,如果发现不符题意,那么就是谎话,否则把所有有关联的同类连接在一起。

    【代码】

    #include<iostream>
    #include<string.h>
    #include<cstdio>
    #include<cstdlib>
    using namespace std;
    long long father[150000+10],t[1000001],ans,o,x,y,k,vis[100001],a,b,c;
    void init(int n){			//三倍空间初始化
        for(int i=1;i<=n;i++)
        {
            father[i]=i;
            father[i+n]=i+n;
            father[i+n+n]=i+n+n;
        }
    }
    int find(int x)			//找祖先
    {
        if(father[x]==x)
    {
            return x;
        }
        else  
        {return father[x]=find(father[x]);}
    }
    void union_(int x,int y){		//模板
        int i=find(x);
        int j=find(y);
        if(i!=j){father[i]=j;}
    }
    int main()
    {
        int n,m,p;
        cin>>n>>m;
        init(n);
        for(int i=1;i<=m;i++)
        {
            cin>>c>>a>>b;
            if(a>n||b>n)		//判断符不符题意
            {
                ans++;
                continue;
            }
            if(c==1)			//同类情况
            {
                if(find(a+n)==find(b)||find(a+n+n)==find(b))	//判断符不符题意
                ans++;
                else
                {
                    union_(a,b);				//同类处理
                    union_(a+n,b+n);
                    union_(a+n+n,b+n+n);
                }
            }
            else
            {
                if(a==b)				//判断符不符题意
                {
                    ans++;
                    continue;
                }
                if(find(a)==find(b)||find(a+n+n)==find(b))
                ans++;
                else
                {
                    union_(a,b+n+n);			//非同类处理
                    union_(b,a+n);
                    union_(a+n+n,b+n);
                }
            }
        }
        cout<<ans<<endl;				//输出
        return 0;
    }
    

    总结:

    1、并查集要注意关系,不能漏建,不能重建,不然会WA。

    2、Find(),merge()的模板要记住,一般进行修改都会在这两个函数里改。

    3、合并的时候有时要注意方向,不要搞反了。

    随便给点题:

    UVA1316 Supermarket

    UVA615 Is It A Tree?

    UVA1197 The Suspects

    P1195 口袋的天空

    P1525 关押罪犯

    POJ 1988 Parity game
    (题解链接:大佬博客

    $ $ 谢谢观赏,给个赞呗qwq

  • 相关阅读:
    Kinect研究文档
    Unity使用Win10语音
    使用unity2017.3 vuforia7摄像头放大的问题
    Unity响应Android的返回键,退出当前Activity
    unity调用Android百度地图
    Unity带参数的协程
    Android jenkins动态参数配置
    如何下载浏览器视频
    mac 如果修改环境变量
    mac如何修改hosts文件
  • 原文地址:https://www.cnblogs.com/hyfhaha/p/10678190.html
Copyright © 2020-2023  润新知