带权并查集
带权并查集
普通的并查集仅仅记录的是集合的关系,这个关系无非是同属一个集合或者是不在一个集合
带权并查集不仅记录集合的关系,还记录着集合内元素的关系或者说是集合内元素连接线的权值
普通并查集本质是不带权值的图,而带权并查集则是带权的图
考虑到权值就会有以下问题:
- 每个节点都记录的是与根节点之间的权值,那么在Find的路径压缩过程中,权值也应该做相应的更新,因为在路径压缩之前,每个节点都是与其父节点链接着,那个Value自然也是与其父节点之间的权值
- 在两个并查集做合并的时候,权值也要做相应的更新,因为两个并查集的根节点不同
例题
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;
}
---- suffer now and live the rest of your life as a champion ----