带权并查集
前言
忽然发现自己怎么能把最后一道题的题意写的这么简单明了 [手动滑稽] 带权并查集,与普通的并查集相比,多了可推算集合内点关系的功能(因此难度也变大了)
- 带权并查集
https://blog.csdn.net/tribleave/article/details/72878239#%E5%B8%A6%E6%9D%83%E5%B9%B6%E6%9F%A5%E9%9B%86
https://agatelee.cn/2017/05/%E5%B8%A6%E6%9D%83%E5%B9%B6%E6%9F%A5%E9%9B%86/
Cube Stacking - POJ 1988
题意
有两种操作,第一种是M a b
,把含有立方体a的堆叠在含有立方体b的堆上面,第二种是C a
,询问立方体a下面有几个立方体。现在对于每个询问操作给出结果
思路
使用并查集维护当前堆的立方体总和sum_tot,和当前立方体下面立方体的数量sum_below,则
在合并操作中(这里将b堆在a上面)传导关系为sum_below[rb] = sum_tot[ra];
,即b的根下面的立方体数量为sum_tot[a],sum_tot[ra] += sum_tot[rb];
,即两堆合并
在路径压缩操作中传导关系为sum_below[x] += sum_below[xft];
,即现在在x下方立方体的数量应为本来在x下方立方体的数量 + x原来的根下方立方体的数量(其在合并操作中已更新)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3e4 + 15;
int ft[N];
int sum_tot[N], sum_below[N];
inline void init(){
for(int i = 1; i < N; i++){
ft[i] = i;
sum_below[i] = 0;
sum_tot[i] = 1;
}
}
int find(int x){
if(ft[x] == x) return x;
int xft = ft[x];
ft[x] = find(ft[x]);
sum_below[x] += sum_below[xft];
return ft[x];
}
void merge(int a, int b){
int ra = find(a), rb = find(b);
if(ra != rb){
ft[rb] = ra;
sum_below[rb] = sum_tot[ra];
sum_tot[ra] += sum_tot[rb];
}
}
int main(){
int n;
char op[2];
while(~scanf("%d", &n)){
init();
while(n--){
scanf("%s", op);
if(op[0] == 'M'){
int a, b;
scanf("%d%d", &a, &b);
merge(b, a);
}else{
int x;
scanf("%d", &x);
find(x);
printf("%d\n", sum_below[x]);
}
}
}
}
Dragon Balls - HDU 3635
题意
每个城市(编号1到n)现在各有1颗龙珠(编号1到n),现在有两种操作
T a b
: 将a城市的龙珠转移到b城市中
Q a
:询问a龙珠所在的城市,a城市此时的龙珠数量,a龙珠的转移次数
思路
并查集应维护城市龙珠数量cnt_sum,和龙珠转移次数cnt_tran
a龙珠所在的城市即ft[a]
a城市此时的龙珠数量是并查集维护的cnt_sum
a龙珠的转移次数是并查集维护的cnt_tran
在路径压缩操作中,龙珠转移次数传导关系为cnt_tran[x] += cnt_tran[xft];
,即当前该龙珠的转移次数为原来根的转移次数 + 该龙珠的转移次数,考虑到原来的根进行路径压缩后不再成为根,因此此这样写不会造成重复加
在合并操作中,城市龙珠数量传导关系为cnt_sum[ra] += cnt_sum[rb];
,即合并,cnt_tran[rb]++;
,即原来的根进行了本次转移,次数+1
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e4 + 15;
int cnt_sum[N], cnt_tran[N];
int ft[N];
inline void init(int n){
for(int i = 1; i <= n; i++){
ft[i] = i;
cnt_tran[i] = 0;
cnt_sum[i] = 1;
}
}
int find(int x){
if(ft[x] == x) return x;
int xft = ft[x];
ft[x] = find(ft[x]);
cnt_tran[x] += cnt_tran[xft];
return ft[x];
}
void merge(int a, int b){
int ra = find(a), rb = find(b);
if(ra != rb){
ft[rb] = ra;
cnt_sum[ra] += cnt_sum[rb];
cnt_tran[rb]++;
}
}
int main(){
int t, csn = 1;
scanf("%d", &t);
while(t--){
int n, q;
scanf("%d%d", &n, &q);
init(n);
char op[2];
int a, b;
printf("Case %d:\n", csn++);
while(q--){
scanf("%s", op);
if(op[0] == 'T'){
scanf("%d%d", &b, &a);
merge(a, b);
}else{
scanf("%d", &a);
find(a);
printf("%d %d %d\n", ft[a], cnt_sum[ft[a]], cnt_tran[a]);
}
}
}
return 0;
}
How Many Answers Are Wrong - HDU 3038
题意
给定一个序列的元素个数n以及信息个数q,对于每个信息,给出区间[a,b]中数的和,问信息中有多少条是矛盾的(若发生矛盾,则认为前面一条是正确的)
思路
几个月前看到这道题,一脸懵逼,现在好多了~
将区间个数和转化为 arr[a] + val = arr[b + 1],于是就变成了带权并查集区间合并的题目,用并查集维护节点到根节点的差值
路径压缩操作中,sum[x] += sum[xft];
,即差值累加
合并操作中,若a与b同属于一个并查集,则计算差值,若与该信息矛盾,则这是一条矛盾的信息,否则进行合并操作sum[rb] = sum[a] + val - sum[b];
,画图可得此传导关系
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e5 + 15;
int sum[N];
int ft[N];
inline void init(int n){
for(int i = 1; i <= n + 1; i++){
ft[i] = i;
sum[i] = 0;
}
}
int find(int x){
if(ft[x] == x) return x;
int xft = ft[x];
ft[x] = find(ft[x]);
sum[x] += sum[xft];
return ft[x];
}
int merge(int a, int b, int val){
int ra = find(a), rb = find(b);
if(ra == rb && val + sum[a] != sum[b]) return 0;
ft[rb] = ra;
sum[rb] = sum[a] + val - sum[b];
return 1;
}
int main(){
int n, q;
while(~scanf("%d%d", &n, &q)){
n++;
init(n);
int a, b, val;
int ans = 0;
while(q--){
scanf("%d%d%d", &a, &b, &val);
b++;
ans += !merge(b, a, val);
}
printf("%d\n", ans);
}
return 0;
}
分数调查 - HihoCoder 1515
思路
与上题是相同的,这里把差值算出来而已
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 15;
int ft[N], rk[N];
inline void init(int n){
memset(rk, 0, sizeof(rk));
for(int i = 1; i <= n; i++) ft[i] = i;
}
int find(int x){
if(ft[x] == x) return x;
int xft = ft[x];
ft[x] = find(ft[x]);
rk[x] += rk[xft];
return ft[x];
}
void merge(int a, int b, int val){
int ra = find(a), rb = find(b);
if(ra != rb){
ft[rb] = ra;
rk[rb] = rk[a] + val - rk[b];
}
}
int main(){
int n, m, q;
while(~scanf("%d%d%d", &n, &m, &q)){
init(n);
while(m--){
int a, b, val;
scanf("%d%d%d", &a, &b, &val);
merge(a, b, val);
}
while(q--){
int a, b;
scanf("%d%d", &a, &b);
if(find(a) == find(b)){
printf("%d\n", rk[b] - rk[a]);
}else{
puts("-1");
}
}
}
}
Parity game - POJ 1733
题意
给定一个序列的长度n以及q个信息,每个信息给出区间[a,b]的和是奇数还是偶数,问第一条矛盾的信息的位置,若都不矛盾输出n
思路
首先题目给的范围太大,要么离散化处理,要么用map
使用并查集维护区间属性,1为奇数,0为偶数
则其传导可用异或实现,剩余内容不赘述
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
using namespace std;
const int N = 2e5 + 15;
map<int, int> sum;
map<int, int> ft;
inline void init(){
sum.clear();
ft.clear();
}
int find(int x){
if(ft[x] == x) return x;
int xft = ft[x];
ft[x] = find(ft[x]);
sum[x] ^= sum[xft];
return ft[x];
}
int merge(int a, int b, int val){
int ra = find(a), rb = find(b);
if(ra == rb && (val ^ sum[a]) != sum[b]) return 0;
ft[rb] = ra;
sum[rb] = sum[a] ^ val ^ sum[b];
return 1;
}
int main(){
int n, q;
while(~scanf("%d%d", &n, &q)){
n++;
init();
int a, b, val;
char op[10];
int ans = 0;
bool flag = 0;
while(q--){
scanf("%d%d%s", &a, &b, op);
if(flag == 1) continue;
b++;
val = (op[0] == 'o');
if(ft.find(a) == ft.end()) ft.insert(pair<int, int>(a, a));
if(ft.find(b) == ft.end()) ft.insert(pair<int, int>(b, b));
if(!merge(b, a, val)) flag = 1;
else ans++;
}
printf("%d\n", ans);
}
return 0;
}
A Bug’s Life - POJ 2492
题意
有n只bug,给出q条信息,表示a、b两只bug的交♂配,问其中是否有gay
思路
用并查集维护当前节点与根节点的关系,0为同性,1为异性(注意不要颠倒,因为根节点与自身为同性)
由下两张图可得传导关系
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
using namespace std;
const int N = 1e6 + 15;
int ft[N];
int sex[N];
inline void init(int n){
for(int i = 1; i <= n; i++){
ft[i] = i;
sex[i] = 0;
}
}
int find(int x){
if(ft[x] == x) return x;
int xft = ft[x];
ft[x] = find(ft[x]);
sex[x] = sex[xft]^sex[x];
return ft[x];
}
int merge(int a, int b){
int ra = find(a), rb = find(b);
if(ra == rb && sex[a] == sex[b]) return 0;
ft[rb] = ra;
sex[rb] = sex[a]^1^sex[b];
return 1;
}
int main(){
int t, csn = 1;
scanf("%d", &t);
while(t--){
int n, q;
scanf("%d%d", &n, &q);
init(n);
int a, b;
int flag = 0;
while(q--){
scanf("%d%d", &a, &b);
if(!merge(a, b)) flag = 1;
}
if(csn > 1) puts("");
printf("Scenario #%d:\n", csn++);
puts(flag ? "Suspicious bugs found!" : "No suspicious bugs found!");
}
return 0;
}