種類並查集學習
阿新 • • 發佈:2020-07-10
種類並查集
學習網站:https://zhuanlan.zhihu.com/p/97813717
種類並查集(包括普通並查集)維護的是一種迴圈對稱的關係。
題目大意:研究表明蟲子都是異性相吸,給你 \(n\) 對蟲子,\(a\) 、\(b\) 表示這兩隻蟲子相互吸引。問是否可以找到兩隻蟲子是同行相吸,如果可以,請輸出“Suspicious bugs found!”,否則請輸出“No suspicious bugs found!”
題解:
這個題目很簡單,可以把所有相同性別的人放到一個圈裡,然後判斷 \(a\) 、\(b\) 是不是在一個圈裡即可。
我們開一個兩倍大小的並查集。例如,假如我們要維護4個元素的並查集,我們改為開8個單位的空間:
\(i+n\) 表示與 \(i\) 的性別不同。
那麼如果 1 和 2 的性別不同,那麼 \(merge(1,n+2)\) \(merge(n+1,2)\)
這樣如果可以通過式子得到同行相吸則一定是在一個圈裡。
#include <cstdio> #include <cstdlib> #define inf 0x3f3f3f3f #define inf64 0x3f3f3f3f3f3f3f3f #define debug(x) printf("debug:%s=%d\n",#x,x); //#define debug(x) cout << #x << ": " << x << endl using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 2e6+10; int f[maxn],a[maxn],b[maxn]; void init(int n){ for(int i=0;i<=2*n;i++) f[i]=i; } int findx(int x){ return f[x]==x?x:f[x]=findx(f[x]); } void merge(int x,int y){ x=findx(x); y=findx(y); if(x==y) return ; f[x]=y; } bool same(int x,int y){ return findx(x)==findx(y); } int main(){ int t; scanf("%d",&t); for(int cas=1;cas<=t;cas++){ int n,m,ans=0; scanf("%d%d",&n,&m); init(2*n); for(int i=1;i<=m;i++){ scanf("%d%d",&a[i],&b[i]); if(same(a[i],b[i])) ans = 1; merge(a[i],b[i]+n); merge(b[i],a[i]+n); } printf("Scenario #%d:\n",cas); if(ans) printf("Suspicious bugs found!\n"); else printf("No suspicious bugs found!\n"); printf("\n"); } }
我們會發現,A、B、C三個種群天然地符合用種類並查集維護的要求。
於是我們可以用一個三倍大小的並查集進行維護,用 \(i+n\) 表示 \(i\) 的捕食物件,而 \(i+2n\) 表示 \(i\) 的天敵。
-
對於 \(a\) 、\(b\) 如果是同類關係,則 \(a+n\) 、\(b+n\) 也是同類, \(a+2*n\) 、\(b+2*n\) 也是同類
-
如果 \(a\) 、\(b\) 如果是捕食關係,則 \(a\) 、 \(b+n\) 也是捕食關係,\(a+n\)、\(b+2*n\) 也是捕食關係
所以只要判斷有沒有矛盾即可。
#include <cstdio> #include <cstdlib> #define inf 0x3f3f3f3f #define inf64 0x3f3f3f3f3f3f3f3f #define debug(x) printf("debug:%s=%d\n",#x,x); //#define debug(x) cout << #x << ": " << x << endl using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 5e5+10; int f[maxn]; void init(int n){ for(int i=0;i<=n;i++) f[i]=i; } int findx(int x){ return f[x]==x?x:f[x]=findx(f[x]); } void merge(int x,int y){ x=findx(x); y=findx(y); if(x==y) return ; f[x]=y; } bool same(int x,int y){ return findx(x)==findx(y); } int main(){ int n,k,ans=0; scanf("%d%d",&n,&k); init(3*n); for(int i=1;i<=k;i++){ int opt,x,y; scanf("%d%d%d",&opt,&x,&y); if(x<=0||y<=0||x>n||y>n||(opt==2&&x==y)) { ans++; continue; } if(opt==1){ if(same(x,y+n)||same(x,y+2*n)) ans++; else merge(x,y),merge(x+n,y+n),merge(x+2*n,y+2*n); } else{ if(same(x,y)||same(x,y+n)) ans++; else merge(x,y+2*n),merge(x+n,y),merge(x+2*n,y+n); } } printf("%d\n",ans); return 0; }
這裡也有滿足種類並查集的關係
我們開一個兩倍大小的並查集。例如,假如我們要維護4個元素的並查集,我們改為開8個單位的空間:
對於罪犯i,i+n為他的敵人
所以如果 1 和 2 是敵人,那麼merge(1,n+2),merge(n+1,2)
表示1要連向2的敵人,1和2的敵人是好朋友
2要連向1的敵人,2和1的敵人是好朋友
只要最後不連成一個圈則表示可以進行合理分配使得沒有衝突事件的影響力
如果連成一個圈,則表示前面分配完不在一個監獄之後當前的a,b一定會處於同一個監獄
因為要衝突事件影響力越小越好,所以貪心的考慮先排影響力大的罪犯
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define inf64 0x3f3f3f3f3f3f3f3f
#define debug(x) printf("debug:%s=%d\n",#x,x);
//#define debug(x) cout << #x << ": " << x << endl
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 5e5+10;
int f[maxn];
struct node{
int a,b,c;
node(int a=0,int b=0,int c=0):a(a),b(b),c(c){}
}e[maxn];
bool cmp(node a,node b){
return a.c>b.c;
}
void init(){
for(int i=0;i<=maxn;i++) f[i]=i;
}
int findx(int x){
return f[x]==x?x:f[x]=findx(f[x]);
}
void merge(int x,int y){
x=findx(x);
y=findx(y);
if(x==y) return ;
f[x]=y;
}
bool same(int x,int y){
return findx(x)==findx(y);
}
int main(){
int n,m,ans=0;
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
e[i]=node(a,b,c);
}
sort(e+1,e+1+m,cmp);
for(int i=1;i<=m;i++){
if(same(e[i].a,e[i].b)) {
ans = e[i].c;
break;
}
merge(e[i].a,e[i].b+n);
merge(e[i].b,e[i].a+n);
}
printf("%d\n",ans);
return 0;
}