并查集
并查集
普通并查集
路径压缩写法:
struct Union_Find_Set {
int f[N];
inline void init() {
for(int i = 1 ; i <= n ; ++ i)
f[i] = i;
}
inline int find(int x) {
if(x != f[x]) f[x] = find(f[x]);
return f[x];
}
inline void merge(int a, int b) {
int x = find(a), y = find(b);
f[y] = f[x];
}
} Set;
启发式合并写法:
struct Union_Find_Set {
int f[N], h[N];
inline void init() {
for(int i = 1 ; i <= n ; ++ i)
f[i] = i, h[i] = 1;
}
inline int find(int x) {
if(x != f[x]) return find(f[x]);
return f[x];
}
inline void merge(int a, int b) {
int x = find(a), y = find(b);
if(h[x] > h[y]) f[y] = f[x];
else {
f[x] = f[y];
if(h[x] == h[y]) ++ h[y];
}
}
} Set;
这是按秩合并的,当然也可以按元素个数启发式合并。
这俩可以写一起:
struct Union_Find_Set {
int f[N], h[N];
inline void init() {
for(int i = 1 ; i <= n ; ++ i)
f[i] = i, h[i] = 1;
}
inline int find(int x) {
if(x != f[x]) f[x] = find(f[x]);
return f[x];
}
inline void merge(int a, int b) {
int x = find(a), y = find(b);
if(h[x] > h[y]) f[y] = f[x];
else {
f[x] = f[y];
if(h[x] == h[y]) ++ h[y];
}
}
} Set;
要注意的是 find
里面的 if
写 return
的复杂度是假的。
例题
多而且杂,一般可用并查集维护的性质很突出。
CF217A Ice Skating
很傻逼的网格图问题。
同列 / 同行放到一个连通块里面,并查集轻松维护。
P2658 汽车拉力比赛
有点傻逼的网格图问题。
分析:
1.由于题目要求保证所有路标相互可达,于是想到并查集
2.发现对于任意一个
3.每次扫网格图,对于每个点,若和其相邻点高度差小于当前二分的
4.坐标
时间复杂度
check
部分代码:
inline bool check(int x) {
init();
for(int i = 1 ; i <= n ; ++ i)
for(int j = 1 ; j <= m ; ++ j) {
//to(i, j) 为坐标的转换函数。
int pos = to(i, j), pos1 = to(i + 1, j), pos2 = to(i, j + 1);
if(i + 1 <= n && abs(a[pos] - a[pos1]) <= x) merge(pos, pos1);
if(j + 1 <= m && abs(a[pos] - a[pos2]) <= x) merge(pos, pos2);
}
int Fa = 0;
for(int i = 1 ; i <= n ; ++ i)
for(int j = 1 ; j <= m ; ++ j)
if(vis[to(i, j)]) {
if(! Fa) Fa = find(to(i, j));
else if(Fa != find(to(i, j))) return false;
}
return true;
}
扩展域并查集(种类并查集)
应用于有多个集合且有关系时。
另外这东西还能判二分图。
具体的就是建多倍点。
例题
P1892 [BOI2003] 团伙
算是板题?
考虑如何去维护关系。我们用
按照题意,每次朋友操作就
那么为什么朋友操作的时候不要
查询代码:
while(m --) {
cin >> op >> x >> y;
if(op == 'E') merge(x, y + n), merge(y, x + n);
else merge(x, y);
}
P2024 [NOI2001] 食物链
种类变成
维护方式和上一题类似,冲突判断的具体方式为:
1.本次操作为同类操作但之前有过捕食操作
2.本次操作为捕食操作但之前有过同类操作或逆向的捕食操作
查询部分代码:
while(m --) {
cin >> op >> x >> y;
if((x == y && op == 2) || x > n || y > n) {
++ ans;
continue;
}
if(op == 1) {
if(find(x) == find(y + n) || find(y) == find(x + n)) ++ ans;
else merge(x, y), merge(x + n, y + n), merge(x + 2 * n, y + 2 * n);
}
else {
if(find(x) == find(y) || find(y) == find(x + n)) ++ ans;
else merge(x, y + n), merge(x + n, y + 2 * n), merge(x + 2 * n, y);
}
}
P5937 [CEOI1999] Parity Game
很好的一道 trick。
首先一段区间
不难发现将区间
考虑怎么去具体地维护奇偶。用种类并查集维护奇偶性,如果当前奇偶性与先前的发生矛盾,则直接退出询问。
注意值域过大,需要离散化。
给出询问的代码:
for(int i = 1 ; i <= q ; ++ i) {
a[i].l = lower_bound(b + 1, b + 1 + tot, a[i].l) - b;
a[i].r = lower_bound(b + 1, b + 1 + tot, a[i].r) - b;
if(a[i].op) {
if(find(a[i].l) == find(a[i].r + tot)) return cout << i - 1, 0;
else {
merge(a[i].l, a[i].r);
merge(a[i].l + tot, a[i].r + tot);
}
}
else {
if(find(a[i].l) == find(a[i].r)) return cout << i - 1, 0;
else {
merge(a[i].l, a[i].r + tot);
merge(a[i].l + tot, a[i].r);
}
}
}
带权并查集
维护边的时候带权。
一般用路径压缩能够减少维护的信息。
合并时候的权值更新可以用向量去理解。
struct Union_Find_Set {
int f[N], val[N];
inline void init() {
memset(val, 0, sizeof val);
for(int i = 1 ; i <= n ; ++ i)
f[i] = i;
}
inline int find(int x) {
if(x != f[x]) val[x] += val[f[x]], f[x] = find(f[x]);
return f[x];
}
inline void merge(int a, int b, int Val) {
int x = find(a), y = find(b);
f[y] = f[x], val[y] = -val[a] + Val + val[b];
}
} Set;
当然操作不仅限于加法。
可撤销并查集
按加入的时间从后往前撤销。
用启发式合并写法实现(路径压缩改变树的形态),同时维护上述操作可以用栈来实现。
那么对于一条边为什么一定要是有顺序的撤销呢?如果不是按出栈的顺序撤销,那么必定有比他晚一些连边的集合的大小没法维护,所以必须按出栈顺序撤销。
struct Union_Find_Set {
int f[N], h[N];
stack<int> s;
inline void init() {
memset(val, 0, sizeof val);
for(int i = 1 ; i <= n ; ++ i)
f[i] = i, h[i] = 1;
}
inline int find(int x) {
if(x != f[x]) return find(f[x]);
return f[x];
}
inline void merge(int a, int b) {
int x = find(a), y = find(b);
if(h[x] > h[y]) f[y] = f[x];
else {
f[x] = f[y];
if(h[x] == h[y]) ++ h[y];
}
}
inline void Delete() {
if(s.empty()) return;
int k = s.top(); s.pop();
h[f[k]] -= h[k], f[k] = k;
}
inline void revoke(int x) {while(s.size() > x) Delete();}
} Set;
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!