并查集习题精讲:从基础到高阶应用

回顾:并查集分为简单并查集、扩展域并查集、带权并查集三种,第一种一般用于解决单元关系问题,后两种可以解决多关系问题。

下面是上篇博客的题单。

简单并查集:

  1. 3367 【模板】并查集 - 洛谷
  2. P1551 亲戚 - 洛谷
  3. P1596 [USACO10OCT] Lake Counting S - 洛谷
  4. P1955 [NOI2015] 程序自动分析 - 洛谷

扩展域并查集: 

  1. P1892 [BalticOI 2003] 团伙 - 洛谷
  2. P2024 [NOI2001] 食物链 - 洛谷

带权并查集: 

  1. P2024 [NOI2001] 食物链 - 洛谷
  2. P1196 [NOI2002] 银河英雄传说 - 洛谷

一、3367 【模板】并查集 - 洛谷

题⽬来源: 洛⾕
题⽬链接: P3367 【模板】并查集
难度系数: ★★

【解法】:模板题~

🖥️code:

#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int fa[N];
int n;

int find(int x)
{
	if(x == fa[x]) return x;
	// 路径压缩 
	return fa[x] = find(fa[x]);
	
	//return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void un(int x, int y) // 不要起union 
{
	int fx = find(x);
	int fy = find(y);
//	修改指向 
	fa[x] = fy;
}

bool issame(int x, int y)
{
	int fx = find(x);
	int fy = find(y);
	return fx == fy;
} 

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i++) fa[i] = i;
	return 0;
 } 

二、P1551 亲戚 - 洛谷

题⽬来源: 洛⾕
题⽬链接: P1551 亲戚
难度系数: ★★
【解法】:并查集的简单应用~
🖥️code:
#include <iostream>

using namespace std;

const int N = 5010;
int n, m, p;
int fa[N];

int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void un(int x, int y)
{
	int fx = find(x);
	int fy = find(y);
//	修改指向 
	fa[fx] = fy;
}

bool issame(int x, int y)
{
	return find(x) == find(y);
}

int main()
{
	cin >> n >> m >> p;
	// init
	for(int i = 1; i <= n; i++) fa[i] = i;
	//un
	for(int i = 1; i <= m; i++)
	{
		int x, y; cin >> x >> y;
		un(x, y);
	}
	// query
	for(int i = 1; i <= p; i++)
	{
		int x, y; cin >> x >> y;
		if(issame(x, y)) cout << "Yes" << endl;
		else cout << "No" << endl;
	}
	return 0;
}

三、P1596 [USACO10OCT] Lake Counting S - 洛谷

题⽬来源: 洛⾕
题⽬链接: P1596 [USACO10OCT] Lake Counting S
难度系数: ★★
【解法】:本题也可以用搜索的floodfill算法解决。求水坑的数量就是找连通的W区域共有多少个,
我们可以用并查集把同一区域的W合并在一起。
问题1:遍历时如何找到与之相同的W? 遍历到W时把右、下、右下、左下方向的W合并在一起。
问题2:如何将二维矩阵信息存储到一维并查集中呢? 二维坐标转一维即可~(注意下标从零开始)。
问题3:如何判断最终有几个水坑? 遍历一下fa数组找有几多个大哥~(自己指向自己的结点)。
🖥️code:
#include <iostream>

using namespace std;

const int N = 110;
char a[N][N];
int n, m;
int fa[N * N]; 
// 方向向量
int dx[] = {1, 1, 0, -1};
int dy[] = {0, 1, 1, 1}; 

int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void un(int x, int y)
{
	fa[find(x)] = find(y);
}

void bfs()
{
	for(int i = 0; i < n; i++)
	{
		for(int j = 0; j < m; j++)
		{
			for(int k = 0; k < 3; k++)
			{
				int x = i + dx[k];
				int y = j + dy[k];
				// 合并时转化成一位坐标 
				if(a[x][y] == 'W' && y >= 0) un(i * m + j, x * m + y);
			}	
		}
	}
}

int main()
{
	cin >> n >> m;
	// init
	for(int i = 0; i < n * m; i++) fa[i] = i;
	
	for(int i = 0; i < n; i++)
	{
		for(int j = 0; j < m; j++)
		{
			cin >> a[i][j];
		}
	}
	
	// 合并水坑 
	bfs();
	
	// 统计数量 
	int ret = 0;
	for(int i = 0; i < n * m; i++)
	{
		if(fa[i] == i && a[i / m][i % m] == 'W') ret++;
	}
	cout << ret << endl;
	return 0;
}

四、P1955 [NOI2015] 程序自动分析 - 洛谷

题⽬来源: 洛⾕
题⽬链接: P1955 [NOI2015] 程序⾃动分析
难度系数: ★★★
【解法】:本题的思路很简单,e = 1时,将其合并;e = 0时,判断是否在同一个集合。
但是本题的数据并不是很友好,我们开不了1e9规模的数组,然而n的规模只有1e5,也就是说:本题的数据很分散,所以我们需要 离散化算法辅助。
🖥️code:
#include <iostream>
#include <unordered_map>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;
int fa[N * 2];
int T, n;

struct node {
	int i, j, e;
}a[N];

// 离散化
int pos, disc[N * 2];
unordered_map<int, int> mp;

int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void un(int x, int y)
{
	int fx = find(x), fy = find(y);
	fa[fx] = fy;
}

bool issame(int x, int y)
{
	return find(x) == find(y);
}

bool check()
{
	// 一定要先合并再判断 
	for (int i = 1; i <= n; i++)
	{
		int x = a[i].i, y = a[i].j, op = a[i].e;
		if (op == 1) un(mp[x], mp[y]);
	}
	for (int i = 1; i <= n; i++)
	{
		int x = a[i].i, y = a[i].j, op = a[i].e;
		if (op == 0)
		{
			if (issame(mp[x], mp[y])) return false;
		}
	}
	return true;
}


int main()
{
	cin >> T;

	while (T--)
	{
		cin >> n;
		// 清空数据 
		pos = 0;
		mp.clear();

		for (int i = 1; i <= n; i++)
		{
			cin >> a[i].i >> a[i].j >> a[i].e;
			disc[++pos] = a[i].i;
			disc[++pos] = a[i].j;
		}
		// 离散化
		sort(disc + 1, disc + 1 + pos);
		int cnt = 0;
		for (int i = 1; i <= pos; i++)
		{
			int x = disc[i];
			if (mp.count(x)) continue;
			cnt++;
			mp[x] = cnt;
		}

		// init 此时cnt表示不相同元素个数 
		for (int i = 1; i <= cnt; i++) fa[i] = i;

		if (check()) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	return 0;
}

五、P1892 [BalticOI 2003] 团伙 - 洛谷

题⽬来源: 洛⾕
题⽬链接: P1892 [BOI2003] 团伙
难度系数: ★★★

【解法】:本题的关系集合有两个,一个是朋友域,一个是敌人域。采用扩展域并查集解题时要注意合并时,把两个域同时合并。

• a 和 b 如果是朋友,那就直接合并在⼀起;
• a 和 b 如果是敌⼈,那就把 a 和 b + n 以及 a + n 和 b 合并在⼀起。

tip:为了方便我们后续统计团伙的数量,在合并时新的根结点统一为朋友域的结点,就当敌人域是假象的。

🖥️code:

#include <iostream>

using namespace std;

const int N = 1010;
int n, m;
// 扩展两个域 
int fa[N * 2];

int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void un(int x, int y)
{
	fa[find(x)] = find(y);
}


int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n * 2; i++) fa[i] = i; 
	while(m--)
	{
		char op;
		int x, y;
		cin >> op >> x >> y;
		if(op == 'E')
		{
			un(x + n, y);
			un(y + n, x);
		}
		else un(x, y);
	}
	int ret = 0;
	for(int i = 1; i <= n; i++)
	{
		if(fa[i] == i) ret++;
	}
	cout << ret << endl;
	return 0;
}

六、P2024 [NOI2001] 食物链 - 洛谷

题⽬来源: 洛⾕
题⽬链接: P2024 [NOI2001] ⻝物链
难度系数: ★★★

【解法】:本题同样也是多关系问题,我们采用扩展域并查集解决。

如果 x 和 y 是同类:  
• x 和 y 是同类
• x + n 与 y + n 是同类
• x + n + n 与 y + n + n 是同类


如果 x 捕⻝ y:
• x + n 与 y 同类
• x 与 y + n + n 同类
• x + n + n 与 y + n 同类

如何验证当前的话与前面冲突?

1. x与y是同类,则x 与y + n 或 y + 2 * n是同类就是假话。

2. x 捕食y,x与y或者y + 2 * n是同类就是假话。

🖥️code:

#include <iostream>

using namespace std;

const int N = 5e4 + 10;
int fa[N * 3];
int n, k;

int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void un(int x, int y)
{
	fa[find(x)] = find(y);
}
bool issame(int x, int y)
{
	return find(x) == find(y);
}


int main()
{
	cin >> n >> k;
	for(int i = 1; i <= n * 3; i++) fa[i] = i;
	int ret = 0;
	while(k--)
	{
		int op, x, y; cin >> op >> x >> y;
		if(x > n || y > n)
		{
			ret++;
			continue;
		}
		// 同类合并 
		if(op == 1)
		{
			if(issame(x, y + n) || issame(x, y + 2 * n))
			{
				ret++;
			} 
			else
			{
		 		un(x, y);
				un(x + n, y + n);
				un(x + 2 * n, y + 2 * n);
			}
		
		}
		// x捕食y 
		else
		{
			if(issame(x, y) || issame(x, y + 2 * n))
			{
				ret++;
			}
			else
			{
				un(x, y + n);
				un(x + n, y + 2 * n);
				un(x + 2 * n, y);
			}
		}
		
	}
	cout << ret << endl;
	return 0;
}

七、P2024 [NOI2001] 食物链 - 洛谷 带权并查集版

题⽬来源: 洛⾕
题⽬链接: P2024 [NOI2001] ⻝物链
难度系数: ★★★
【解法】:
🖥️code:
#include<iostream>

using namespace std;

const int N = 5e4 + 10;

int fa[N], d[N];
int n, k;

int find(int x)
{
	if(x == fa[x]) return x;
	int t = find(fa[x]);
	d[x] += d[fa[x]];
	return fa[x] = t;
}
void un(int x, int y, int w)
{
	int fx = find(x), fy = find(y);
	if(fx != fy)
	{
		fa[fx] = fy;
		d[fx] = d[y] + w - d[x];
	}
}

int main()
{
	cin >> n >> k;
	for(int i = 1; i <= n; i++)
	{
		fa[i] = i;
		d[i] = 0;
	}
	int ret = 0;
	while(k--)
	{
		int op, x, y; cin >> op >> x >> y;
		if(x > n || y > n) ret++;
		else
		{
			if(op == 1)
			{
				if(find(x) == find(y) && ((d[y] - d[x]) % 3 + 3) % 3!= 0) ret++;
				else
				{
					un(x, y, 0);
				}
			}
			else
			{
				if(find(x) == find(y) && ((d[y] - d[x]) % 3 + 3) % 3 != 1) ret++;
				else
				{
					un(x, y, 2);
				}
			}
		}
	}
	cout << ret << endl;
	return 0;
}

八、P1196 [NOI2002] 银河英雄传说 - 洛谷

题⽬来源: 洛⾕
题⽬链接: P1196 [NOI2002] 银河英雄传说
难度系数: ★★★

🖥️code:

#include <iostream>

using namespace std;

const int N = 3e4 + 10;
int n = 3e4;
int fa[N], d[N], cnt[N];

int find(int x)
{
	if(fa[x] == x) return x;
	int t = find(fa[x]);
	d[x] += d[fa[x]];
	return fa[x] = t;
}

void un(int x, int y)
{
	int fx = find(x), fy = find(y);
	if(fx != fy)
	{
		fa[fx] = fy;
		d[fx] += cnt[fy];
		cnt[fy] += cnt[fx];
	}
}

int query(int x, int y)
{
	int fx = find(x), fy = find(y);
	if(fx == fy) return abs(d[y] - d[x]) - 1;
	else return -1;
}

int main()
{
	for(int i = 1; i <= n; i++)
	{
		fa[i] = i;
		cnt[i] = 1;
	}
	int T; cin >> T;
	while(T--)
	{
		char op; int x, y; cin >> op >> x >> y;
		if(op == 'M')
		{
			un(x, y);
		}
		else
		{
			cout << query(x, y) << endl;
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值