并查集 = =

用处:
1、将两个集合合并

2、询问两个元素是否在同一个集合当中

(这两个操作的时间复杂度都是近乎O(1)的)

基本原理:
每个集合用一棵树来表示,树的编号就是树根的编号。

每个节点存储它的父结点,用p数组来存,p[x]就表示x的父结点。

问题:

1、如何判断树根?

     令除了根节点之外,p[x]都不等于x。

2、如何求x的集合编号?

     从x一路往上走,走到树根就可。

while(p[x] != x)  x = p[x]; // 只要x不是树根我就一直往上走,直到走到树根为止

3、如何合并两个集合编号?

     假设px是x的集合编号,py是y的集合编号。合并即令px = y。

板子:

(1)朴素并查集

int p[N]; // 存储每个点的祖宗点

//返回x的祖宗节点
int find(int x)
{
	if(p[x] != x)  p[x] = find(p[x]);
	return p[x]; 
} 

//初始化,假定节点编号是1~n
int i;
for(i = 1;i <= n;i ++)  p[i] = i;

//合并a和b所在的两个集合
p[find(a)] = find(b);

(2)维护size的并查集

int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的意义, 表示祖宗节点所在集合中的点的数量

//返回x的祖宗节点
int find(int x)
{
	if(p[x] != x)  p[x] = find(p[x]);
	return p[x];
} 

//初始化,假定节点编号是1~n
int i;
for(i = 1;i <= n;i ++)
{
	p[i] = i;
	size[i] = 1;
}

//合并a和b所在的两个集合
size[find(b)] += size[find(a)];
p[find(a)]  = find(b);

(3)维护到祖宗节点距离的并查集

int p[N], d[N];
//p[]存储每个点的祖宗节点,d[x]存储x到p[x]的距离

//返回x的祖宗节点
int find(int x)
{
	if(p[x] != x)
	{
		int u = find(p[x]);
		d[x] += d[p[x]];
		p[x] = u;
	}
	return p[x];
} 

//初始化,假定节点编号是1~n 
int i;
for(i = 1;i <= n;i ++)
{
	p[i] = i;
	d[i] = 0;
}

//合并a和b所在的两个集合
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题, 初始化find(a)的偏移量 

题目:

题目1:(合并集合)

题目描述:

一共有 n个数,编号是 1∼n,最开始每个数各自在一个集合中。

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

  1. M a b,将编号为 a和 b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  • Q a b,询问编号为 a和 b的两个数是否在同一个集合中;
  • 输入格式

    第一行输入整数 n和 m。

    接下来 m行,每行包含一个操作指令,指令为 M a bQ a b 中的一种。

  • 输出格式

    对于每个询问指令 Q a b,都要输出一个结果,如果 a和 b

    在同一集合内,则输出 Yes,否则输出 No

    每个结果占一行。

  • 数据范围

    1≤n,m≤105

    输入样例:

    4 5
    M 1 2
    M 3 4
    Q 1 2
    Q 1 3
    Q 3 4
    

    输出样例:

    Yes
    No
    Yes
    

AC代码:
 

#include<cstdio>
#include<iostream>

using namespace std;

const int N = 1e6 + 10;

int p[N]; // father数组:存储每个节点的父节点是谁 
//初始的时候每个元素都是单独一个集合即每个集合里面只有一个点,这个点的树根就是自己


int find(int x) // 寻找祖宗节点 + 路径压缩 
{
	if(p[x] != x)  p[x] = p[find(x)]; // 如果x不是根节点的话,就然让它的父结点等于它的祖宗节点
	return p[x]; // 返回父结点 
} 
int main()
{
	int n,m;
	cin >> n >> m;
	 
	for(int i = 1 ; i <= n ; i ++)  p[i] = i; // 初试时把每个节点的p值赋成自己 
    
    while(m --)
    {
    	char op[2];
    	int a,b;
    	cin >> op >> a >> b;
    	
    	if(op[0] == 'M')  p[find(a)] = find(b);
    	else 
    	{
    		if(find(a) == find(b))  cout << "YES" << endl;
    		else  cout << "NO" << endl;
		}
	}
	return 0;
}

题目2:(连通块中点的数量)

题目描述:

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

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

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

输入格式:

第一行输入整数 n和 m。

接下来 m行,每行包含一个操作指令,指令为 C a bQ1 a bQ2 a 中的一种。

输出格式

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

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

每个结果占一行。

数据范围

1≤n,m≤105

输入样例:

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

输出样例:

Yes
2
3

AC代码:
 

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int p[N]; // father数组:存储每个节点的父节点是谁 
//初始的时候每个元素都是单独一个集合即每个集合里面只有一个点,这个点的树根就是自己

int number[N]; // 存储集合的元素数量  // 规定:我们只会看根节点的number


int find(int x) // 寻找祖宗节点 + 路径压缩 
{
	if(p[x] != x)  p[x] = find(p[x]); // 如果x不是根节点的话,就然让它的父结点等于它的祖宗节点
	return p[x]; // 返回父结点 
} 
int main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(false);
    
	int n,m;
	cin >> n >> m;
	 
	for(int i = 1 ; i <= n ; i ++)  
	{
	    p[i] = i; // 初试时把每个节点的p值赋成自己
		number[i] = 1; // 最开始的时候每个集合只有一个点 
    }
    while(m --)
    {
    	string op;
    	int a,b;
    	cin >> op;
    	
    	if(op == "C")  
		{
			cin >> a >> b; 
			if(find(a) == find(b))  continue; // a和b已经在同一集合 
			number[find(b)] += number[find(a)];
			p[find(a)] = find(b);
		}
    	else if(op == "Q1") 
    	{
    		cin >> a >> b;
    		if(find(a) == find(b))  cout << "Yes" << endl;
    		else  cout << "No" << endl;
		}
		else
		{
			cin >> a;
			cout << number[find(a)] << endl;
		}
	}
	return 0;
}

题目3:(无线网络)

题目描述:

一个二维平面中放着 n台电脑。电脑编号 1∼n。每台电脑的具体位置已知。

初始时,所有电脑都是关机状态。两台电脑可以直接通信当且仅当两台电脑都处于开机状态,并且两电脑之间距离不超过 d。

电脑之间还可以通过中介电脑实现间接通信。

例如,电脑 A既可以与电脑 B 实现直接通信,也可以与电脑 C 实现直接通信,那么电脑 B 和电脑 C 就可以借助电脑 A

实现间接通信。

现在,要按顺序进行若干个操作,操作共分为两种:

  • O p,将电脑 p开机。
  • S p q,询问电脑 p和电脑 q之间能否实现通信。
  • 输入格式

    第一行包含两个整数 n,d。

    接下来 n行,每行包含两个整数 xi,yi,表示一台电脑的位置坐标。

    接下来若干行,每行包含一个操作命令,格式入题面所述。

  • 输出格式

    对于每个询问指令,输出一行答案,如果可以实现通信,则输出 SUCCESS,否则输出 FAIL

    数据范围

    1 ≤ n ≤ 1001

    ,
    0 ≤ d ≤20000,
    0 ≤ xi,yi ≤ 10000,
    1 ≤ p , q ≤ , q ≠ p
    最多包含 3×10^5个操作指令。

  • 输入样例:

    4 1
    0 1
    0 2
    0 3
    0 4
    O 1
    O 2
    O 4
    S 1 4
    O 3
    S 1 4
    

    输出样例:

    FAIL
    SUCCESS
    

AC代码:

#include<bits/stdc++.h>

using namespace std;

typedef pair<int, int> coo; 

const int N = 1e4 + 10;

coo a[N];
int p[N],book[N]; // book数组用来标记电脑状态,表示关闭,表示打开

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

// 计算距离
double dis(coo a, coo b)
{
	return (double)sqrt((a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second));
}

int main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(false);
    
    int n,d;
    cin >> n >> d;
    for(int i = 1 ; i <= n ; i ++)
    {
    		int x,y;
    		cin >> x >> y;
    		a[i] = {x,y};
	}
    
    for(int i = 1 ; i <= n ; i ++)  p[i] = i; // 初始化
    
    string s;
    while( cin >> s)
    {
    	if(s == "O")
    	{
    		int id;
    		cin >> id;
    		
    		book[id] = 1; // 电脑打开
			for(int i = 1 ; i <= n ; i ++)
			{
				if(i == id)  continue; // 一台你说怎么连
				else if(book[i] == 1 && dis(a[id],a[i]) <= d) // 电脑是打开的状态并且他们之间的距离小于d
				{
					p[find(id)] = find(i); // 两台电脑联系起来
				}
			} 
			 
		}
		else
		{
			int u,v;
			cin >> u >> v;
			
			if(find(u) != find(v))  cout << "FAIL" << endl; // u和v是不是一个祖宗节点
			else  cout << "SUCCESS" << endl;
		}
	}
	return 0;
}

题目 4:(可疑人员)

题目描述:

某学校共有 n 个学生,编号 0∼n−1。

该学校共有 m个社团。

每个学生可以加入多个社团,也可以不参加任何社团。

该学校所在的地区爆发了非典型肺炎。经查实,0号学生为病毒携带的可疑人员,需要进行隔离观察。为了防止疫情传播,学校还规定,一旦某社团出现病毒携带的可疑人员,则该社团的全体成员将全部被划分为病毒携带的可疑人员。请你计算,该学校最终共有多少名学生被判定为病毒携带的可疑人员。

输入格式

输入包含多组测试数据。

每组数据第一行包含两个整数 n,m。

接下来 m行,每行描述一个社团的人员,首先包含一个整数 k,表示该社团的人数,然后包含 k

个整数,表示每个社员的编号。

当输入 n=0,m=0时,表示输入结束。

输出格式

每组数据输出一行结果,表示被判定为病毒携带的可疑人员的学生数量。

数据范围

输入最多包含 10组数据。
1≤n≤30000,
0≤m≤500,
1≤k≤n。

输入样例:

100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0

输出样例:

4
1
1

AC代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#define inf 0x3f3f3f3f

using namespace std;

const int N = 30010;

int p[N];

int find(int x)
{
    if (p[x] != x)  p[x] = find(p[x]);
    return p[x];    
}

int main()
{
    int n,m; // 学校总人数,社团的数量 
    
    while (~scanf("%d %d",&n,&m) && (m != 0 || n != 0))
    {
        
        for (int i = 0; i < n; i++)  p[i] = i; // 初始化并查集
        
        while (m--)
        {
            int k;
            scanf("%d",&k); //社团内部的元素数量
            
            int x;
            scanf("%d",&x); // 输入该社团的第一个成员
            
            for (int i = 2; i <= k; i++)    
            {
                int y;
                scanf("%d",&y); // 依次输入社团后面的成员
                
                p[find(x)] = find(y);  // 将该社团的后面点全部与第一个点合并
            }
        }

        int cnt = 0;
        for (int i = 0; i < n; i++) // 遍历所有学生
        {
            if(find(i) == p[0])  cnt ++;
        }
        printf("%d\n",cnt);
    }   

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

21RGHLY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值