目录
并查集
定义
运用情况
注意事项
解题思路
AcWing 239. 奇偶游戏
题目描述
运行代码
代码思路
改进思路
并查集
定义
并查集(Disjoint Set Union,简称DSU),是一种树形的数据结构,常用于处理一些不交集的合并及查询问题。在并查集中,元素被分成多个不相交的集合,每个集合由一个代表元素表示,通过一系列的合并(Union)和查找(Find)操作来维护这些集合的状态。
运用情况
- 连通性问题:判断两个元素是否属于同一个集合,常用于图的连通分量问题。
- 动态连接问题:在元素集合之间建立连接,同时保持快速查询元素间的连接状态。
- 最小生成树:Kruskal算法中用于检测边加入后是否形成环路。
- 游戏算法:如迷宫生成、棋盘类游戏等,判断区域的联通性。
注意事项
- 路径压缩:为了提高查找效率,可以使用路径压缩技术,使得每次查找后路径上的所有节点都直接指向根节点。
- 按秩合并:在合并两个集合时,将秩较小的集合的根节点作为较大集合的子节点,可以保持树的平衡,降低树的高度,从而加快查找速度。
- 内存管理:并查集的数组通常会占用较大的空间,需要合理规划内存使用,特别是在大规模数据处理中。
解题思路
- 初始化:为每个元素创建一个单独的集合,并且每个元素都是其所在集合的根节点。
- 查找(Find):找到某个元素所在集合的代表元素,一般使用递归或迭代实现。
- 合并(Union):将两个集合合并成一个集合,通常选择一个集合的根节点作为新集合的根节点。
AcWing 239. 奇偶游戏
题目描述
239. 奇偶游戏 - AcWing题库
运行代码
#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] != t)
{
res = i - 1;
break;
}
}
else
{
p[pa] = pb;
d[pa] = d[a] ^ d[b] ^ t;
}
}
cout << res << endl;
return 0;
}
代码思路
-
初始化:
- 使用
unordered_map
存储每个节点映射到的唯一ID,这样可以处理任意数量的节点,而不仅仅是从0开始编号的连续整数。 n
和m
分别代表节点的数量和边的数量,但实际中n
在运行时动态增加。- 每个节点的父节点
p[i]
初始化为其自身,表示它们各自构成独立的集合。 d[i]
数组存储从节点i
到其根节点路径上的“奇偶”属性,初始值为0。
- 使用
-
读取输入:
- 读取边的数量
m
和节点间的连接信息。 - 对于每条边,获取或分配节点的ID,读取边的类型(奇数或偶数)。
- 读取边的数量
-
并查集操作:
- 对于每条边,检查两个节点是否已经在同一集合中,如果是,则检查它们之间的“奇偶”属性是否符合当前边的类型。
- 如果不符合,则提前终止并输出矛盾发生的边序号减一。
- 如果两个节点不在同一集合中,执行合并操作,更新父节点和“奇偶”属性。
-
输出结果:输出最后一次有效的边序号或者如果发现矛盾则输出矛盾前的边序号。
改进思路
-
减少哈希查找:目前
get
函数每次都会检查unordered_map
是否包含键,这会导致额外的时间开销。可以通过预处理所有节点的映射,减少运行时的哈希查找次数。 -
空间优化:
unordered_map
在最坏情况下可能导致较高的空间开销。如果节点编号是连续的,可以考虑使用线性数组代替哈希表。 -
并查集优化:虽然代码中没有明确展示,但可以通过按秩合并和路径压缩来进一步优化并查集的性能。
-
预处理节点映射:在读取边之前,先读取所有节点,为它们分配ID,并存储在一个数组中,之后不再使用
unordered_map
。 -
空间优化:如果节点编号是连续的,使用数组替代
unordered_map
来存储节点ID映射。 -
并查集性能改进:在
find
函数中添加路径压缩逻辑,确保每次查找后路径上的所有节点都直接指向根节点,这可以显著减少未来查找的深度。