基础知识
基本操作
// 初始化
void init(){
for(int i = 1; i <= n; i++)
p[i] = i;
}
// 查询(路径压缩)
int find(int x){
return x == p[x] ? x : (p[x] = find(p[x]));
}
// 普通合并
void union(int x, int y){
int fx = find(x), fy = find(y);
if(fx != fy) p[fx] = fy;
}
启发式合并
- 由于合并时希望操作元素尽量少,就让少的往大的合并,这就是启发式合并
- \(n\) 个元素和 \(m\) 次查询,时间复杂度为 \(O(mlogn)\)
// 启发式合并
void union(int x, int y){
int fx = find(x), fy = find(y);
if(fx == fy) return;
if(sz[fx] > sz[fy])
swap(fx, fy);
p[fx] = fy;
sz[fy] += sz[fx];
}
按深度合并
- 每次合并将深度小的一方合并到深度大的一方
- 路经压缩时,可能破坏深度值,复杂度不变差
// 按深度合并
void union(int x, int y){
int fx = find(x), fy = find(y);
if(fx == fy) return;
if(dep[fx] > dep[fy])
swap(fx, fy);
p[fx] = fy;
if(dep[fx] == dep[fy]) // 只有深度相等才更新
dep[fy]++;
}
时间复杂度
- 启发式合并和深度合并,\(n\) 个元素和 \(m\) 次查询,时间复杂度为 \(O(mlogn)\)
- 一般来说并查集时间复杂度为 \(O(m*\alpha (m, n))\)。其中 \(\alpha\) 为阿克曼函数的反函数,可以认为是一个小常数
- 无启发式合并,只路径压缩最坏时间复杂度为 \(O(mlogn)\),平均复杂度为 \(O*\alpha(m,n)\)。
- 可以直接认为 \(O(m)\)
带权并查集
int d[N], p[N];
void find(int x){
if(x == p[x]) return;
int root = find(p[x]);
d[x] += d[p[x]];
p[x] = root;
return p[x];
}
习题
带权并查集 + 背包DP
POJ1417
题意
一个村庄有两类人,好人坏人, 好人总是说真话, 坏人总是说假话, 给你n个询问和好人、坏人的数量p q, 每个询问 x y yes/no, 表示 x 说 y 是 好/坏人。问是否能够唯一确定哪些是好人, 哪些是坏人, 如果可以输出好人的序号以"end"结尾, 否则输出"no"
补充:
- 首先这题我们是知道好人和坏人的各自数量,
- 其次题目中可能会形成若干个集合(表示不是连通的一个图)
- 每个集合中的好人和坏人都是相对关系无法确定
思路:
见代码注释
Solution
#include
#include
#include
#include
#include
#include
#include
#include
贪心+并查集加速
题意
超市里有 \(N\) 个商品. 第 \(i\) 个商品必须在保质期(第 \(d_i\)天)之前卖掉, 若卖掉可让超市获得 \(p_i\) 的利润.
每天只能卖一个商品.
现在你要让超市获得最大的利润.
思路
- 对价格排序,贪心从高往低选,标记使用了的时间,有冲突就找下一个没有冲突的时间点。
- 找下一个没有冲突的时间点就是个并查集加速的过程
Solution
#include
#include
#include
#include
#include
#include
#include
#include
带权并查集维护曼哈顿距离,离线执行
题意
有 \(n\) 个网格状的农田,每个农田之间有距离,会依次给出关系,在给出关系后询问两个农田之间的曼哈顿距离是多少?
若无法判断则输出 \(-1\) 。
思路
- 对于曼哈顿距离的维护,可以开两个数组记录横纵坐标
- 询问按时间排序后,输出时还原原来的顺序
Solution
#include
#include
#include
#include
#include
#include
#include
#include
带权并查集+暴力枚举思想
题意
给了 \(n\) 个小朋友,分成三个组进行石头剪刀布,每个组的小朋友只能出固定的手势。而其中会有裁判可以出任意手势。现在给出了 \(m\) 次对局
每次对局只知道两个人之间的胜负。判断其中是否有唯一的裁判,如果有多个输出Can not determine, 有一个输出对应编号和至少多少行可以判定他是裁判,或者没有裁判。
数据范围: \(1 \leq n\leq 500,\; 0\leq m\leq 2000\)
思路
- 很容易想到边权 % 3 的并查集来维护三组小朋友。
- 根据数据范围,考虑暴力枚举哪个人是裁判,除开他参与的对局看是否有矛盾,有矛盾则不是裁判,否则就是。
- 最后判断裁判个数,如果有唯一裁判,至少多少行判出他是裁判 \(<->\) 判断其他人不是裁判的最大行数(小思维点)
Solution
#include
#include
#include
#include
#include
#include
#include
#include