一般的并查集,维护的是具有连通性、传递性的关系,例如亲戚的亲戚是亲戚。但是,有时候,我们要维护另一种关系:敌人的敌人是朋友。种类并查集就是为了解决这个问题而诞生的。
敌人关系是可以扩展的:捕食等等
例题洛谷P152 关押罪犯
开一个两倍大小的并查集。
开几倍要看有几种关系
例如,假如我们要维护4个元素的并查集,我们改为开8个单位的空间:
用1~4维护朋友关系(就这道题而言,是指关在同一个监狱的狱友),用5~8维护敌人关系(这道题里是指关在不同监狱的仇人)。现在假如我们得到信息:1和2是敌人,应该怎么办?
我们merge(1, 2+n), merge(1+n, 2);。这里n就等于4,但我写成n这样更清晰。对于1个编号为i的元素,i+n是它的敌人。所以这里的意思就是:1是2的敌人,2是1的敌人。
假如我们又知道2和4是敌人,我们merge(2, 4+n), merge(2+n, 4);
这里,1和4通过2+n这个元素间接地连接起来了——也就是敌人的敌人就是朋友。这就是种类并查集工作的原理。
int fa[40005], rank[40005]; //以下为并查集int find(int a){return (fa[a] == a) ? a : (fa[a] = find(fa[a]));}int query(int a, int b){return find(a) == find(b);}void merge(int a, int b){int x = find(a), y = find(b);if (_rank[x] >= _rank[y])fa[y] = x;elsefa[x] = y;if (_rank[x] == _rank[y] && x != y)_rank[x]++;}void init(int n){for (int i = 1; i <= n; ++i){_rank[i] = 1;fa[i] = i;}}
模板和并查集没什么区别,具体区别在于使用merge的时候
例题
L2-010 排座位 (25 分)
#include<bits/stdc++.h>using namespace std;int n,m,k;//种类并查集int fa[2550],_rank[2550];int g[105][105];int find(int a){return a==fa[a]?a:(fa[a] = find(fa[a]));}void merge(int a, int b){int x = find(a), y = find(b);if (_rank[x] >= _rank[y])fa[y] = x;elsefa[x] = y;if (_rank[x] == _rank[y] && x != y)_rank[x]++;}int query(int a, int b){int f1=find(a),f2=find(b),f200=find(b+100);if(f1 == f2){if(g[a][b])return 3;return 1;}else{if(g[a][b])return 4;return 2;}}void print(int q){if(q==1)cout<<"No problem\n";else if(q==2)cout<<"OK\n";else if(q==3)cout<<"OK but...\n";else if(q==4)cout<<"No way\n";}int main(){ios::sync_with_stdio(false);cin>>n>>m>>k;for(int i = 0; i<2550;i++){fa[i]=i;_rank[i]=1;}for(int i = 1; i<= m; i++){int a, b, f;cin>>a>>b>>f;if(f==1){merge(a,b);}else{g[a][b]=g[b][a]=1;}}for(int i = 1;i<=k;i++){int a,b;cin>>a>>b;int q = query(a,b);print(q);}return 0;}
题目中所说朋友的朋友也是朋友,使用并查集把他们并到一个朋友圈中,但两个人只能有一种关系,故朋友和敌人不能用一个容器来记录,应该分开记录
