简介
我们还可以在并查集的边上定义某种权值和这种权值在路径压缩时产生的运算,从而解决更多的问题。也称为「种类并查集」或「拓展域并查集」。
实现
为了维护并查集中的边权,需要将边权下放到子节点中存储。因此,每个节点存储的都是它到它的父节点之间的边权。只有当一个节点的父节点发生变化时,才需要相应地调整边权。一般情形中,这可能发生在路径压缩和合并两个节点时。
例题 三值逻辑
题解
可以将 U,T 看作两个点
首先可以预处理出点 \(i(1\le i \le n)\) 由哪个点 \(from_i\) 的初始值决定,且 \(i\) 与 \(from_i\) 之间的边权为 \(w_i\)。
之后跑一次带权并查集,从 \(1\) 到 \(n\) 遍历每一条边,因为要使所有边的限制都满足,所以如果遍历到一条边,边两端的端点都已经在同一个集合中了,且两点间的边权异或值不是当前边的边权,那么就需要把整个集合都设为 U。
最后统计根是 U 的个数即可。
code
注意,写带权并查集时,对于点 x,要先执行 f[x]=find(f[x]) 来计算 f[x] 到根的边权异或和,但是这时 f[x] 就是根而不是原来的 f[x],所以要先用 tmp 将原本的 f[x] 记录下来,具体见代码。
#include<bits/stdc++.h>
#define fo(a, b, c) for(int b = a; b <= c; b++)
#define N 1000010
using namespace std;
int c, T, n, m;
int fa[N], p[N], from[N], w[N];
int find(int x){if(x == fa[x]) return x;int tmp = fa[x];return fa[x] = find(fa[x]), p[x] ^= p[tmp], fa[x];
}
void solve(){cin >> n >> m;fo(1, i, n + 2) fa[i] = i, p[i] = 0, from[i] = i, w[i] = 0;fo(1, i, m){int x, y; char op;cin >> op >> x;if(op == '+' || op == '-') cin >> y;if(op == '+') from[x] = from[y], w[x] = w[y];if(op == '-') from[x] = from[y], w[x] = w[y] ^ 1;if(op == 'T') from[x] = n + 1, w[x] = 0;if(op == 'U') from[x] = n + 2, w[x] = 0;if(op == 'F') from[x] = n + 1, w[x] = 1;}fo(1, i, n){int x = i, y = from[i], fx = find(x), fy = find(y);if(fx != fy) fa[fx] = fy, p[fx] = p[x] ^ p[y] ^ w[x]; else if(p[x] ^ p[y] ^ w[x]) fa[fx] = n + 2, p[fx] = 0;}int ans = 0;fo(1, i, n){if(find(i) == n + 2) ans++;}cout << ans << "\n";
}
int main(){ios::sync_with_stdio(0);cin.tie(0); cout.tie(0);cin >> c >> T;while(T--) solve();return 0;
}
