算法提高课 并查集

基础并查集

操作:
主要:

  1. 合并两个集合
  2. 查询某个元素的祖宗节点
    如何操作请移步: 并查集的模板们
    优化:
    1 路径压缩
    2 按秩合并(不太常用)

优化

路径压缩:
无论以上那个操作,路径压缩的时间复杂度都是O(log(n)),一般来说肯定够用了,但是不同数据还是不一样哈,比如平衡树,不过在并查集中因为计算很小,所以够用。
按秩合并:
不止并查集,其他很多也可以,它的主要思想:合并两个集合时,每次把节点个数或树的深度更小的那个合并到较大的那个里去。单独使用按秩合并的时间复杂度也是== O(log(n))==。

把两个优化放在一起,也就是理论上的并查集最好的写法:
时间复杂度是O(α(n))
只要0≤n≤21987(Y总说反正是个很大的数)
那么这个O(α(n))都小于等于5!!

扩展

最常用的扩展有两个
1.在维护这两个操作过程中,去记录一下每个集合的大小,因为每个集合大小都是唯一的,所以利用这一思想将这个集合的大小的属性绑定到根节点上。
2. 在维护这两个操作过程中,去记录一下每个节点到根节点的距离。因为这个距离是不变的,所以利用这个,将距离绑定到集合中的每个元素上。

习题

AcWing 1250.格子游戏

链接 :AcWing. 1250. 格子游戏

(为什么还有人玩不知道谁赢了游戏?)
不是博弈论啊,朋友们
如果各位看见这种不会,但是有特殊请况输出的,直接输出这个,可以骗分
思路:
把这个图中的每一个点看成图论中的点,每一条边看成图论中的边,每次画边是把点相连,问题是什么时候会出现环呢?
等价于这两个点在相连之前就在同一个集合当中了
假设我们有了三个连起来的边
假设我们有了三个连起来的边,那么这个再连一下就OK,但是如果我有两个点在连起来之前不在一个集合里,那么它就不可能会形成一个环。
那么我们会发现,能不能连成一个环取决于连接的两个点有无在一个集合当中。
解决这个问题就是并查集问题,从后往前合并,合并到第一个环为止。
还有一个问题,并查集需要把二维坐标转换成一维的

把二维坐标转化成一维有个好用的方法(x, y)=> (x * n + y)
但是要保证x, y都从0开始,所以需要一个映射

#include <bits/stdc++.h>
using namespace std;

const int N = 40010;

int n, m;
int p[N];

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

int get(int x, int y)
{
	return x * n + y;
}
int main()
{
	cin >> n >> m;
	
	for(int i = 0; i < n * n; i ++) p[i] = i; // 并查集初始化;
	
	int res = 0;
	for(int i = 1; i <= m; i ++)
	{
		int x, y; // 坐标,采用最最普通的编号方式,假设一个3 × 3 的矩阵,编号同下
		          // 0 1 2
		          // 3 4 5
		          // 6 7 8
		char d;                                                         
		int b;                                                                      
		cin >> x >> y >> d;
		x --,y --;  //需要做一个映射,x = x * n + y,这个公式必须满足x , y 从0开始         
		int a = get(x, y); 
		if(d == 'D') b = get(x + 1, y); //求终点编号,分两种情况,1.往下走,即 d == 'D';
		else b = get(x, y + 1);//2. 向右走
		
		//看有无形成环就是看两个组中节点是否一样
		int pa = find(a), pb = find(b);
		if(pa == pb)
		{
			res = i;
			break;
		}
		p[pa] = pb;
	}
	if(!res) puts("draw");
	else cout << res << endl;
	
	return 0;
	
}

格子游戏渡劫成功!

AcWing 1252. 搭配购买

链接: AcWing 1252. 搭配购买
悄咪咪:怎么还强买强卖?
第一眼:01背包问题
确实,这个是对的!
而且呢还是个并查集
注意:搭配是相互的
凡是在一个联通块的云朵,要么全买,要么全不买
所以,利用并查集找出联通块,再做一遍01背包即可渡劫!!
第一步:把所有有边相连的点合并在一起
第二步:把每个联通块看成一个物品
第三步:做一遍01背包
总体积和总价值可以同时维护
和并查集维护集合大小是一样的

#include <bits/stdc++.h>

using namespace std;

const int N = 10010;
int n, m, vol;
int v[N], w[N];
int p[N];
int f[N];
int find(int x)
{
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}

int main()
{
	cin >> n >> m >> vol;
	
	for(int i = 1; i <= n; i ++)
	{
		p[i] = i;
	}
	for(int i = 1; i <= n; i ++)
	{
		cin >> v[i] >> w[i];
	}
	
	while (m --)
	{
		int a, b;
		cin >> a >> b;
		int pa = find(a), pb = find (b);
		if(pa != pb)
		{
			v[pb] += v[pa];
			w[pb] += w[pa];
			p[pa] = p[pb];
			
		}
		
	}
	//01 背包
		for(int i = 1; i <= n; i ++)
		{
			if(p[i] == i)
			{
				for(int j = vol; j >= v[i]; j --)
				{
					f[j] = max(f[j], f[j - v[i]] + w[i]);
				}
			}
		}
		cout << f[vol] << endl;
		return 0;
}

ok,搭配购买渡劫成功

AcWing 237.程序自动分析

这道题尽量不要用传递闭包,时间复杂度会很高,是O(n3),过不去
链接: AcWing 237.程序自动分析
我也想让程序自动分析TVT
这个题数据量范围太大,所以第一步需要离散化
作用是排序、判重、二分
这个题目还有许多特点:

  1. 约束条件顺序无所谓,所以先考虑所有相等约束(绝对不矛盾),假设xi = yi,我们可以说,xi和yi在同一个集合中。
  2. 不等条件若想成立,必须满足xi和xj不在同一集合里。
    所以总体思路就是:相等放在同一集合,不等判断是否在同一集合,若在,那么矛盾,若不在就不矛盾。
#include <bits/stdc++.h>

using namespace std;
const int N =  200010;

int n, m;
int p[N];
unordered_map<int, int> S;
struct Query
{
	int x, y ,e;
}query[N];
int get(int x)
{
	if(S.count(x) == 0) S[x] = ++ n;
	return S[x];
}
int find(int x)
{
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}
int main()
{
	int T;
	scanf("%d", &T);
	while (T --)
	{
		n = 0;
		S.clear();
		scanf("%d", &m);
		for(int i = 0; i < m; i ++)
		{
			int x, y ,e;
			scanf("%d%d%d", &x, &y, &e);
			query[i] = {get(x), get(y), e};
			
		}
		
		for(int i = 1; i <= n; i ++) p[i] = i; 
		
		//合并所有约束条件;
		for(int i = 0; i < m; i ++)
		{
			if(query[i].e == 1)
			{
				int pa = find(query[i]. x), pb = find(query[i].y);
				p[pa] = pb;
			} 
		} 
		
		//检查所有不等条件
		bool has_conflict = false;
		for(int i = 0; i < m; i ++)
		{
			if(query[i].e == 0)
			{
				int pa = find(query[i].x), pb = find(query[i].y);
				if(pa == pb)
				{
					has_conflict = true;
					break;
				}
			}
		} 
		if(has_conflict) puts("NO");
			else puts("YES");
	}
	return 0;
posted @   Auditorymoon  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律

喜欢请打赏

扫描二维码打赏

了解更多

点击右上角即可分享
微信分享提示