染色法&并查集:维护不想交集合森林(维护传递性)
染色法
对于p1551亲戚,可并查集,这里考虑朴素染色法

通过颜色来判断是否为同一宗族,颜色相同即同一个
最开始每个人都有一个颜色,当AB为亲戚时,则要将AB染成一个颜色
复杂度:O(数学公式: $ n^{2} $),因为每次操作可能都为O(n)
这里存在的优化:启发式合并
启发式合并:每次选人少部分染色(合并到更多)
染色法的改进:
染色法改进:不去真的执行染色,将B的标记指向A,然后说其应染成A的颜色

link 1 2,则将1,2连接,让2指向1,让3指向2····
即2应染为1颜色,3应染为2的颜色(则3最终应染成1的颜色),通过记录bin数组,记录了每个点应被染的颜色,最终求出整个图的染色方法
bin[1] = 0是它的不要染色,另一种写法是它自己指向自己(则说明它应该被染成1自己这种颜色

bin数组即为并查集
并查集:维护传递性
对于图上DFS/BFS单次操作O(n),当只关心元素是否连通不考虑层级时,可以使用并查集(路径压缩)

并查集:
- 合并:将两个元素(集合)合并为一个集合
- 查询:查询两个元素是否在同一个集合中
路径压缩:细长树一次查询变为短粗树
int find(int x) {//路径压缩
return fa[x] == x ? x :
fa[x] = find(fa[x]);//因为根结点指向自己
}
void merge(int x, int y) {//合并:插入元素并到根结点
fa[find(x)] = find(y);
}
对于查询是否属于同一个集合,也可以使用find(),通过指向根结点可以有效降低树高
查询时,集合中元素无法保证严格执行find操作时,考虑find(x) == find(y)写法
并查集的优化(按秩合并):对于合并,显然是想让小的集合合并到大的集合上,按秩合并O(mα(m, n)),对于10^100量级操作<=5,线性
可以按照人数多少和树的深度判读
新的问题是无法在压缩时很好的更新秩,所以按秩合并时即不使用路径压缩
int find(int x) {//按秩合并后不使用路径压缩
return fa[x] == x ? x : find(fa[x]);
}
void merge(int x, int y) {//将y合并到x里面
x = find(x), y = find(y);
if(x != y) {
if(sz[x] < sz[y]) {//让y为秩小,将y插入x
swap(x, y);
}
fa[y] = x;
sz[x] += sz[y];//x家庭增加y家庭大小
}
}
两种方式对比:
路径压缩易于书写,但破坏了原本树形结构
有些题即依赖原本树形结构,对于可持久化数组即依赖于原本树形结构
按秩合并保留了树结构,最坏时复更优,但代码量稍大
常见应用:

对于扩展域还可以维护复杂关系:如敌人的敌人是我的朋友···
P3958奶酪:并查集维护传递性构造集合,那么判断连通性判断是否在同一个集合即可
维护连通,对于中间空洞认为若连通则合并起来,对于题中的上下表面,采取建立虚拟结点,对虚拟结点进行连通即处理了上下表面问题,最后查看上下表面是否连通(同一集合)即可

点击查看代码
for(int i = 1; i <= n + 2; ++ i) fa[i] = i;//n+2存入虚拟结点
for(int i = 1; i <= n; ++ i) {
cin >> x[i] >> y[i] >> z[i];
if(z[i] + r >= h) merge(i, n + 2);//上表面
if(z[i] - r <= 0) merge(i, n + 1);//下表面
for(int j = 1; j <= i; ++ j) {
if(getdis(x[i], y[i], z[i], x[j], y[j], z[j]) <= 4LL * r * r) {//判断距离
merge(i, j);
}
}
put(find(n + 1) == find(n + 2) ? "Yes" : "No");
}
判断距离:sqrt时,满足小于为2r即可
当为了消除sqrt的精度问题,采用(x - x1)^2+(y - y1)^2+(z- z1)^2 <= 2r^2等价的思路
带权并查集:路径压缩 + 前缀和:带权并查集
链接里面是大佬写的带权值并查集
浙公网安备 33010602011771号