并查集模板

并查集的应用:

1.以树作为节点的组织结构,结构的形态很是否采取优化策略有很大关系,未进行优化的树结构可能会是“畸形”树(严重不平衡,头重脚轻,退化成链表等),按尺寸(正规说法叫做秩,后文全部用秩来表示)进行平衡,同时辅以路径压缩后,树结构会高度扁平化。
2.虽然组织结构比较复杂,数据表示方式却十分简洁,主要采用数组作为其底层数据结构。一般会使用两个数组(parent-link array and size array),分别用来保存当前节点的父亲节点以及当前节点所代表子树的秩。第一个数组(parent-link array)无论是否优化,都需要使用,而第二个数组(size array),在不需要按秩合并优化或者不需要保存子树的秩时,可以不使用。根据应用的不同,可能需要第三个数组来保存其它相关信息,比如HDU-3635中提到的“转移次数”。
3.主要操作包括两部分,union以及find。union负责对两颗树进行合并,合并的过程中可以根据具体应用的性质选择是否按秩优化。需要注意的是,执行合并操作之前,需要检查待合并的两个节点是否已经存在于同一颗树中,如果两个节点已经在一棵树中了,就没有合并的必要了。这是通过比较两个节点所在树的根节点来实现的,而寻找根节点的功能,自然是由find来完成了。f
 

p[N],在此表示每个节点的父节点;及在进行find之后,该节点对应的祖先节点。

初始化:每个节点都是自己父节点,p[i]=i;

 

代码:

#include <iostream>

using namespace std;

const int N=100010;

int p[N];

int n,m;

//查找祖先节点(祖先节点p[x]==x)
int find(int x)
{
    if(x!=p[x]) p[x]=find(p[x]);
    
    return p[x];
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) p[i]=i;
    
    while(m--)
    {
        char op;
        int x,y;
        cin>>op>>x>>y;
        
         int px=find(x),py=find(y);
        if(op=='M')  //合并俩个集合
        {
          
            if(px!=py)
            {
                p[px]=py;  //将
            }
        }
        else
        {
            if(px==py) puts("Yes");
            else
            puts("No");
        }
    }
    
    
    
    return 0;
}

 

例题二:连通块中点的数量

给定一个包含n个点(编号为1~n)的无向图,初始时图中没有边。

现在要进行m个操作,操作共有三种:

  1. “C a b”,在点a和点b之间连一条边,a和b可能相等;
  2. “Q1 a b”,询问点a和点b是否在同一个连通块中,a和b可能相等;
  3. “Q2 a”,询问点a所在连通块中点的数量;

输入格式

第一行输入整数n和m。

接下来m行,每行包含一个操作指令,指令为“C a b”,“Q1 a b”或“Q2 a”中的一种。

输出格式

对于每个询问指令”Q1 a b”,如果a和b在同一个连通块中,则输出“Yes”,否则输出“No”。

对于每个询问指令“Q2 a”,输出一个整数表示点a所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3

 思路:

每个连通块就是一颗树,可以用并查集来做。需要用一个size数组来记录当前联通块中的点的数量,保证点的数量只存在整颗数的根节点处。这样加的时候才不会重复累加。

代码:

#include <iostream>

using namespace std;

const int N=100010;

int p[N],sizes[N];
int n,m;

//查找树根
int find(int x)
{
    if(x!=p[x]) p[x]=find(p[x]);
    
    return p[x];
}


int main()
{
    cin>>n>>m;
    //初始化
    for(int i=1;i<=n;i++)
    {
        p[i]=i;
        sizes[i]=1;
    }
    while(m--)
    {
        char op[2];
        int a,b;
        cin>>op;
        if(op[0]=='C')
        {
            cin>>a>>b;
            int pa=find(a),pb=find(b);
            if(pa!=pb)
            {
                p[pa]=pb;
                sizes[pb]+=sizes[pa]; //将a的所有节点累加到b的根节点上
            }
        }
        else if(op[1]=='1')
        {
            cin>>a>>b;
            if(find(a)==find(b)) puts("Yes");
            else puts("No");
        }
        else
        {
            cin>>a;
            cout<<sizes[find(a)]<<endl;
        }
    }
    
    return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值