基础算法--并查集

0 阅读3分钟

并查集


并查集也是我们在数据结构中学过的,它是基于树型结构,解决一些不相交的集合的合并和查询问题。

下面是一道模板题:

并查集.png

下面是代码示例:

#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-->没关系

此外,种类并查集的种类也可以更多,比如食物链中可以分为天敌/同类/猎物。

下面我们一起来看一道有点难度的题目:

并查集-1.png

分析: 按仇恨值从大到小排序,依次尝试把仇人分到两个监狱(利用 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。