带权并查集

带权并查集

普通的并查集仅仅记录的是集合的关系,这个关系无非是同属一个集合或者是不在一个集合

带权并查集不仅记录集合的关系,还记录着集合内元素的关系或者说是集合内元素连接线的权值

普通并查集本质是不带权值的图,而带权并查集则是带权的图

考虑到权值就会有以下问题:

  1. 每个节点都记录的是与根节点之间的权值,那么在Find的路径压缩过程中,权值也应该做相应的更新,因为在路径压缩之前,每个节点都是与其父节点链接着,那个Value自然也是与其父节点之间的权值
  2. 在两个并查集做合并的时候,权值也要做相应的更新,因为两个并查集的根节点不同

例题

D - How Many Answers Are Wrong

题意: 给定一个长度为n的数列以及m条信息,每条信息包含三个数 l,r,v,表示数列的[l,r]区间和为v.这些信息中有的是正确信息,有的是错误信息。对于每一条信息,如果其与之前出现的正确信息有逻辑矛盾,那么它就是错误的,否则是正确的。现在请你判断这m条信息中有多少条错误信息。

样例

Sample Input

10 5
1 10 100
7 10 28
1 3 32
4 6 41
6 6 1

Sample Output

1

题解:

本题利用带权并查集,选择各个端点作为并查集的元素,当两个端点之间的边权值已知时,将两个端点纳入同一个集合,将右端点作为父结点

首先定义每一个元素的信息

// 每一个元素的信息包括 1.父结点 2.到父节点的边权
// 故建立两个数组
#define Maxsize 200000+1
int f[Maxsize];
int dis[Maxsize];

初始化函数

void init(int n){
  for(int i = 0; i <= n; i++){ //从0开始,因为要记录[0,?]的边权
    f[i] = i;
    dis[i] = 0; // 一开始父结点就是自己,自己到自己的边权是0
  }
}

查找函数

int find_f(int a){
  if(f[a] == a){
    return a;
  }else{
    int ori_f = f[a];
    f[a] = find_f(f[a]);
    dis[a] += dis[ori_f]; // 这里画图可以推导出方程
  }
}

合并函数

void union_f(int l,int r,int val){ // 当已知l~r的边权之后,调用此函数进行合并
  int lf = find_f(l);
  int rf = find_f(r);
  if(lf < rf){
    f[lf] = rf;
    dis[lf] = dis[r] - dis[l] + val;
    // -------l -----------lf ---  r -------- rf
    //        |------ val-- -------|
    //        |-- dis[l]---|
    //                             |--dis[r]---|
    //   dis[lf] = dis[r] + val - dis[l]
      
      
    // -------l -----------r --- lf -------- rf
    //        |----val-----|
    //        |--------dis[l]----|
    //                     |------dis[r]-----|
    //  dis[lf] = dis[r] + val - dis[l]
  }else{
    f[rf] = lf;
    dis[rf] = dis[l] - dis[r] - val;
    // -------l---------r------------rf------lf
    //        |---val---|
    //        |-----------dis[l]--------------|
    //                  |---dis[r]---|
    // dis[rf] = dis[l] - dis[r] - val
  }
}

判断函数

bool same_f(int a,int b){
  return find_f(a) == find_f(b);
}

完整代码

#include <iostream>
#include <algorithm>

#define MaxN 200000+1
#define MaxM 40000+1

using namespace std;

int f[MaxN];
int dis[MaxN];

void init(int n){
    for(int i = 0; i <= n; i++){
        f[i] = i;
        dis[i] = 0;
    }
}

int find_f(int a){
    if(f[a] == a){
        return a;
    }else{
        int ori_f = f[a];
        f[a] = find_f(f[a]);
        dis[a] += dis[ori_f];
        return f[a];
    }
}

void union_f(int l,int r,int val){ // 当已知l~r的边权之后,调用此函数进行合并
    int lf = find_f(l);
    int rf = find_f(r);
    if(lf < rf){
        f[lf] = rf;
        dis[lf] = dis[r] - dis[l] + val;
    }else{
        f[rf] = lf;
        dis[rf] = dis[l] - dis[r] - val;
    }
}

int same_f(int l,int r){
    return find_f(l) == find_f(r);
}
int main(){
    int n,m,l,r,val;
    while (cin >> n >> m) {
        int ans = 0;
        init(n);
        for (int i = 0; i < m; i++) {
            cin >> l >> r >> val;
            l--; // 点权换边权
            if(same_f(l,r)){
                if(dis[l]-dis[r] != val){
                    ans++;
                }
            }else{
                union_f(l,r,val);
            }
        }
        cout << ans << endl;
    }
    return 0;
}

参考 https://www.cnblogs.com/zhxmdefj/p/11117791.html

posted @ 2020-07-16 10:30  popozyl  阅读(185)  评论(0编辑  收藏  举报