算法提高课 并查集
基础并查集
操作:
主要:
- 合并两个集合
- 查询某个元素的祖宗节点
如何操作请移步: 并查集的模板们
优化:
1 路径压缩
2 按秩合并(不太常用)
优化
路径压缩:
无论以上那个操作,路径压缩的时间复杂度都是O(log(n)),一般来说肯定够用了,但是不同数据还是不一样哈,比如平衡树,不过在并查集中因为计算很小,所以够用。
按秩合并:
不止并查集,其他很多也可以,它的主要思想:合并两个集合时,每次把节点个数或树的深度更小的那个合并到较大的那个里去。单独使用按秩合并的时间复杂度也是== O(log(n))==。
把两个优化放在一起,也就是理论上的并查集最好的写法:
时间复杂度是O(α(n))
(只要0≤n≤21987(Y总说反正是个很大的数)
那么这个O(α(n))都小于等于5!!)
扩展
最常用的扩展有两个
1.在维护这两个操作过程中,去记录一下每个集合的大小,因为每个集合大小都是唯一的,所以利用这一思想将这个集合的大小的属性绑定到根节点上。
2. 在维护这两个操作过程中,去记录一下每个节点到根节点的距离。因为这个距离是不变的,所以利用这个,将距离绑定到集合中的每个元素上。
习题
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
这个题数据量范围太大,所以第一步需要离散化
作用是排序、判重、二分
这个题目还有许多特点:
- 约束条件顺序无所谓,所以先考虑所有相等约束(绝对不矛盾),假设xi = yi,我们可以说,xi和yi在同一个集合中。
- 不等条件若想成立,必须满足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;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律