带权并查集
带权并查集
用途
维护联通块中元素的相对关系
如:
有长度为 \(n\) 的序列,\(A_1,A_2,A_3,\ldots,A_n\) 。
-
1 a b w:加入一个约束\(A_a-A_b=w\)
-
2 a b:询问\(A_a-A_b\)
不仅如此,还能判断给定的约束是否冲突,询问可不可求
讲解
定义
只有能获取以下信息的运算,才能被带权并查集维护
抽象关系为:
struct Node {
};
抽象合并为:
Node op(Node a, Node b)
抽象逆元为:
Node inv(Node a)
抽象自指为:
Node self(int i)
关系
表示并查集中点 \(i\) 与 点 \(j\) 的关系,可以为 \(\oplus\) ,\(-\) 等等
比如 \(A_i-A_j=Node\) 或 \(A_i\oplus A_j=Node\) \(\dots\)
合并
表示该如何合并两段关系,以减法举例:
故对减法,我们令:
Node op(Node a, Node b) {
return Node(a + b);
}
同理,对于 \(\oplus\) :
Node op(Node a, Node b) {
return Node(a ^ b);
}
逆元
表示一段关系的逆向,以减法举例:
故对减法,我们令:
Node inv(Node a) {
return -a;
}
同理:对于 \(\oplus\) :
Node inv(Node a) {
return a;
}
自指
这是描述自己与自己的关系的量。
通常我们设为被维护运算的幺元。
故对减法和 \(\oplus\),我们都令:
Node self(int i) {
return 0;
}
实现过程
这是一个普通的并查集:
struct DSU {
vector<int> fa;
DSU(int x) {
fa.resize(x);
iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
if (fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
bool check(int x, int y) {
int fx = find(x), fy = find(y);
return fx == fy;
}
void merge(int x, int y) {
int fx = find(x);
int fy = find(y);
if (fx == fy)
return;
fa[fy] = fx;
}
};
把每个部分依次修改为带权并查集。
外部定义
vector<int> fa;
vector<Node> val;
我们需要一个数组维护关系,其中 \(val_i\) 表示 \(i\) 与 \(fa_i\) 的关系,即:
构造函数
DSU(int x) {
fa.resize(x);
iota(fa.begin(), fa.end(), 0);
val.resize(x);
for(int i = 0;i < x;i++) {
val[i] = self(i);
}
}
我们需要额外初始化维护的权值为它自己和自己的关系。
find
int find(int x) {
if (fa[x] == x)
return x;
int f = fa[x];
fa[x] = find(f);
val[x] = op(val[x], val[f]);
return fa[x];
}
此步需要推导:
现在的关系:
我们进行路径压缩:
注意:我们必须把它上面的路径合并完,才能合并它和根,故先 \(find\) 后赋值 \(val\),每次我们都认为递归返回后 \(fa[x]\) 已经是根。
check
这个不变。
query
pair<bool, Node> query(int a, int b) {
int ra = find(a);
int rb = find(b);
if (ra != rb)
return {false, self(1)};
Node res = op(val[a], inv(val[b]));
return {true, res};
}
这是我们需要新增的函数,用于查询 \(a\) 与 \(b\) 两点的相对关系。
若它们不属于一个联通块,说明现在的信息不足以求解。
否则:
由于已经执行过 \(find\) 的路径压缩,若两点属于一个联通块,它们的深度必然为 \(2\) 。
所以一定有 \(fa[a] = fa[b] = root\) ,故我们知道:
使用 \(op\) 合并即可求解。
merge
bool merge(int a, int b, Node d) {
int ra = find(a);
int rb = find(b);
if (ra == rb) {
return query(a, b).second == d;
}
fa[ra] = rb;
val[ra] = op(op(inv(val[a]), d), val[b]);
return true;
}
添加约束:
如果信息已经足以解出 \(a \xrightarrow{d} b\) 中的 \(d\) ,那就要解出来,用于判断这次 \(merge\) 是废话,还是产生了冲突。
否则,我们合并 \(a\) 和 \(b\) 两个块的信息,执行完 \(fa[ra] = rb\) 后,现在的情况是:
我们不知道 \(x\) 是什么,需要求解它。
我们先进行转化:
故:
合并即可。
STD
struct V_DSU {
struct Node {
int x;
Node(int x = 0) { this->x = x; }
bool operator==(const Node &other) const { return x == other.x; }
};
Node self(int i) {
Node res;
// SELF
return res;
}
Node op(Node a, Node b) {
Node res;
// OP
return res;
}
Node inv(Node a) {
Node res;
// INV
return res;
}
vector<int> fa;
vector<Node> val;
V_DSU(int x) {
fa.resize(x);
iota(fa.begin(), fa.end(), 0);
val.resize(x);
for (int i = 0; i < x; i++) {
val[i] = self(i);
}
}
int find(int x) {
if (fa[x] == x)
return x;
int f = fa[x];
fa[x] = find(f);
val[x] = op(val[x], val[f]);
return fa[x];
}
pair<bool, Node> query(int a, int b) {
int ra = find(a);
int rb = find(b);
if (ra != rb)
return {false, self(a)};
Node res = op(val[a], inv(val[b]));
return {true, res};
}
bool merge(int a, int b, Node d) {
int ra = find(a);
int rb = find(b);
if (ra == rb) {
return query(a, b).second == d;
}
fa[ra] = rb;
val[ra] = op(op(inv(val[a]), d), val[b]);
return true;
}
};
浙公网安备 33010602011771号