并查集
并查集也是我们在数据结构中学过的,它是基于树型结构,解决一些不相交的集合的合并和查询问题。
下面是一道模板题:
下面是代码示例:
#include <iostream>
using namespace std;
int fa[200005];
/*int find(int x){//时间复杂度为O(n),在一些样本集下可能会TLE
if(x == fa[x]){
return x;
}
else{
return find(fa[x]);
}
}*/
int find(int x){
if(x == fa[x]){
return x;
}
else{
return fa[x] = find(fa[x]);//路径压缩,同一并查集内所有孩子的父节点都是同一个,而不是链式结构
}
}
int main(){
int n, m, z, x, y, fx, fy;
cin >> n >> m;
//初始化
for(int i = 1; i <= n; ++i){
fa[i] = i;
}
for(int i = 1; i <= m; ++i){
cin >> z >> x >> y;
if(z == 1){
//合并
fx = find(x);
fy = find(y);
fa[fx] = fy;//或fa[fy] = fx
}
else{
//查询
fx = find(x);
fy = find(y);
if(fx == fy){
cout << "Y\n";
}
else{
cout << "N\n";
}
}
}
}
种类并查集
并查集:维护的连通性(传递性),亲戚的亲戚也是亲戚
种类并查集(扩展域并查集):合并 查询 敌对关系
- x和y是朋友-->x所在的树和y所在的树合并
- x和y是敌人-->x和y的敌人合并,y和x的敌人合并 但是我们怎么知道x和y的敌人是谁呢?我们可以引入“假想敌”方便我们进行分类。假如有1 2 3 4 5 6,引入假想敌分别是7 8 9 10 11 12。(i和i+6是敌人)
此时我们来判断4 5: if(f5 == f4)-->朋友 if(f5 == f10 && f4 == f11)-->敌人 else-->没关系
此外,种类并查集的种类也可以更多,比如食物链中可以分为天敌/同类/猎物。
下面我们一起来看一道有点难度的题目:
分析: 按仇恨值从大到小排序,依次尝试把仇人分到两个监狱(利用 x 和 x+n 的对立关系,需要建立假想敌)。第一个无法分开的仇人对的仇恨值,就是答案。
下面是代码示例:
#include <iostream>
#include <algorithm>
using namespace std;
int f[40005];//记录集合内每一个节点的根节点
struct crime{
int a, b, c;
} e[100005];
bool cmp(crime& x, crime& y){
return x.c > y.c;
}
int find(int x){
if(x == f[x]){
return x;
}
else{
return f[x] = find(f[x]);//路径压缩,同一并查集内所有孩子的父节点都是同一个,而不是链式结构
}
}
int main(){
int n, m, fa, fb, fad, fbd;
cin >> n >> m;
//初始化
for(int i = 1; i <= 2*n; ++i){//注意这里是2n,因为还有假想敌
f[i] = i;//一开始根节点就是自己
}
for(int i = 1; i <= m; ++i){
cin >> e[i].a >> e[i].b >> e[i].c;
}
sort(e+1, e+m+1, cmp);//把仇恨从大到小排列
for(int i = 1; i <= m; ++i){
fa = find(e[i].a);
fb = find(e[i].b);
fad = find(e[i].a+n);
fbd = find(e[i].b+n);
if(fa == fb){//检查a和b是否是在同一个监狱内
cout << e[i].c << endl;
return 0;
}
f[fa] = fbd;//把a这一脉,与b的敌人合并(敌人的敌人是朋友)
f[fb] = fad;//把b这一脉,与a的敌人合并
}
cout << 0 << endl;
return 0;
}
如果还想强化这类题目,可以去做一做洛谷的P2024。