并查集
①合并两个集合
②查询某个元素的祖宗节点
查找
通俗地讲一个故事:几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。
在这样的思想下,并查集的查找算法诞生了。
路径压缩
这样的确可以达成目的,但是显然效率实在太低。为什么呢?因为我们使用了太多没用的信息,我的祖先是谁与我父亲是谁没什么关系,这样一层一层找太浪费时间,不如我直接当祖先的儿子,问一次就可以出结果了。甚至祖先是谁都无所谓,只要这个人可以代表我们家族就能得到想要的效果。把在路径上的每个节点都直接连接到根上,这就是路径压缩。
合并
宴会上,一个家族的祖先突然对另一个家族说:我们两个家族交情这么好,不如合成一家好了。另一个家族也欣然接受了。
我们之前说过,并不在意祖先究竟是谁,所以只要其中一个祖先变成另一个祖先的儿子就可以了。
① 记录每个集合的大小 -》 绑定到根节点
② 记录每个点到根节点的距离 -》 绑定到每个点身上
AcWing 1250. 格子游戏
输入样例:
3 5 1 1 D 1 1 R 1 2 D 2 1 R 2 2 D
输出样例:
4
问题等价于:在一次次连边的过程中,什么时候第一次连成一个环
可以将其看成是图论中的点跟边,出现环的时候,等价于,两个点在连通之前就已经在同一个连通块当中
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 400010; int n, m; int p[N]; // 坐标映射 int get(int x, int y) { return x * n + y; } int find(int x) { if(p[x] != x) p[x] = find(p[x]); return p[x]; } int main() { cin >> n >> m; for(int i = 1; i <= n * n; i ++ ) p[i] = i; int res = 0; for(int i = 1; i <= m; i ++ ) { int x, y; char d; cin >> x >> y >> d; x --, y -- ; int a = get(x, y); int b; if(d == 'D') b = get(x + 1, y); else b = get(x, y + 1); 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. 搭配购买
输入样例:
5 3 10 3 10 3 10 3 10 5 100 10 1 1 3 3 2 4 2
输出样例:
1
把每一个联通块看做是一个物品,然后就变成了一个01 背包问题了。
#include <cstring> #include <iostream> #include <algorithm> 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; }
AcWing 237. 程序自动分析
输入样例:
2 2 1 2 1 1 2 0 2 1 2 1 2 1 1
输出样例:
NO YES
思路
1.读入数据并加入离散化数组
2.离散化后去重,并用此数组的个数初始化并查集
3.按照先相等后不等的顺序,先维护所有的相等关系;最后在依次查看每一对不等关系是否出现矛盾坑点
1.一开始的数组大小要开两倍
2.离散化之后注意映射是从0开始还是从1开始
3.并查集初始化有一个小技巧,初始化的数量直接是alls数组离散化去重之后的元素个数(下标从1开始映射)
4.离散化的查询和合并操作都应该在映射的数上进行操作
#include <cstring> #include <algorithm> #include <iostream> #include <cstdio> #include <unordered_map> using namespace std; const int N = 2000010; 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; cin >> T; while(T -- ) { n = 0; cin >> m; S.clear(); 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] = p[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; }
AcWing 238. 银河英雄传说
输入样例:
4 M 2 3 C 1 2 M 2 4 C 4 2
输出样例:
-1 1
① 不问间隔多少战舰 -》 并查集
② 同时维护间隔多少战舰 -》 统一维护当前战舰到排头的距离
③
![]()
d[x] 表示 x 到 p[x] 的距离
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 30010;
int m;
int p[N], sizes[N], d[N];
int find(int x)
{
if(p[x] != x)
{
int root = find(p[x]);
d[x] += d[p[x]];
p[x] = root;
}
return p[x];
}
int main()
{
cin >> m;
for(int i = 1; i < N; i ++ )
{
p[i] = i;
sizes[i] = 1;
}
while(m -- )
{
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if(op[0] == 'M')
{
int pa = find(a), pb = find(b);
if (pa != pb) { // 新加的,不在一个集合中才合并!!!
d[pa] = sizes[pb]; // pa 到 pb 的距离为 pb 中节点的个数
sizes[pb] += sizes[pa]; // 更新 pb 中节点的个数
p[pa] = pb; // 将 pa 合并到 pb 中
}
}
else
{
int pa = find(a), pb = find(b);
if(pa != pb) puts("-1");
else printf("%d\n", max(0, abs(d[a] - d[b]) - 1));
}
}
return 0;
}
AcWing 239. 奇偶游戏
输入样例:
10 5 1 2 even 3 4 odd 5 6 even 1 6 even 7 10 odd
输出样例:
3
来源:《算法竞赛进阶指南》, POJ1733 , kuangbin专题
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N = 20010;
int n, m;
int p[N], d[N];
unordered_map<int, int> S;
int get(int x)
{
if (S.count(x) == 0) S[x] = ++ n;
return S[x];
}
int find(int x)
{
if (p[x] != x)
{
int root = find(p[x]);
d[x] += d[p[x]];
p[x] = root;
}
return p[x];
}
int main()
{
cin >> n >> m;
n = 0;
for (int i = 0; i < N; i ++ ) p[i] = i;
int res = m;
for (int i = 1; i <= m; i ++ )
{
int a, b;
string type;
cin >> a >> b >> type;
a = get(a - 1), b = get(b);
int t = 0;
if (type == "odd") t = 1;
int pa = find(a), pb = find(b);
if (pa == pb)
{
if (((d[a] + d[b]) % 2 + 2) % 2 != t)
{
res = i - 1;
break;
}
}
else
{
p[pa] = pb;
d[pa] = d[a] ^ d[b] ^ t;
}
}
cout << res << endl;
return 0;
}