帶權並查集
帶權並查集是結點存有權值信息的並查集。權值使關係可以量化,也就是說,權值代表着當前節點與父節點的某種關係,通過兩者關係,也可以將同一棵樹下兩個節點的關係表示出來。而一般並查集只能判斷屬於某個集合。
種類並查集
一般並查集可以判斷一種關係,即屬於這種關係或不屬於這種關係,比如朋友的朋友是朋友。但比如敵人的敵人是朋友這種涉及兩種以上的關係就會用到種類並查集,其實就是用多個並查集來模擬種類。
例題
傳送門: POJ-1401
動物王國中有三類動物A,B,C,這三類動物的食物鏈構成了有趣的環形。A喫B, B喫C,C喫A。
現有N個動物,以1-N編號。每個動物都是A,B,C中的一種,但是我們並不知道它到底是哪一種。
有人用兩種說法對這N個動物所構成的食物鏈關係進行描述:
第一種說法是"1 X Y",表示X和Y是同類。
第二種說法是"2 X Y",表示X喫Y。
此人對N個動物,用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。
1) 當前的話與前面的某些真的話衝突,就是假話;
2) 當前的話中X或Y比N大,就是假話;
3) 當前的話表示X喫X,就是假話。
你的任務是根據給定的N(1 <= N <= 50,000)和K句話(0 <= K <= 100,000),輸出假話的總數。
input:
第一行是兩個整數N和K,以一個空格分隔。
以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。
若D=1,則表示X和Y是同類。
若D=2,則表示X喫Y。
output:
只有一個整數,表示假話的數目。
Sample Input:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output:
3
分析
-
帶權並查集解法
定義權值數組rela[]描述與根節點的關係,0表示同類,1表示當前點能喫別人,2表示當前點被別人喫。通過向量思想維護root[]和rela[]關係來判斷與之前關係是否矛盾。
ps: 詳細向量圖解 -
種類並查集解法
開一個3n大小的數組,用x、x+n、x+2n來表示3個不同的種類。比如x+n代表x的天敵,x+2n是x+n的天敵,x是x+2n的天敵然後模擬維護數組並判斷即可。(當然代表獵物也可以,源碼用的天敵)
代碼
- 帶權並查集解法
#include<cstdio>
using namespace std;
const int maxn = 50004;
int root[maxn], rela[maxn];
int r, x, y, n, k;
int Find(int x) {
if (x == root[x])return x;
int tmp = root[x];
root[x] = Find(root[x]);//壓縮路徑
//x與根節點關係=x到父節點的關係+父節點(tmp)到根節點的關係
rela[x] = (rela[x]+rela[tmp]+ 3) % 3;
return root[x];
}
bool check(int r, int x, int y) {
if (x > n || y > n || (r == 1 && x == y))
return false;
if (Find(x) == Find(y))
//判斷x與y關係(r)等於x到根關係+根到y關係(~rela[y])
return r == (rela[x] - rela[y] + 3) % 3;
else return true;
}
void Union(int r, int x, int y) {
int fx = Find(x), fy = Find(y);
if (fx != fy) {
root[fx] = fy;//x根併入y根集合
//更新x根到新根節點關係
//x根到新根節點關係=根到x的關係(~relx[x])+x與y關係(r)+y到根關係
rela[fx] = (-rela[x] + r + rela[y] + 3) % 3;
}
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 0; i <= n; i++) {//初始化
root[i] = i;
rela[i] = 0;
}
int ans = 0;
while (k--) {
scanf("%d%d%d", &r, &x, &y);
r--;
if (check(r, x, y))
Union(r, x, y);
else ans++;
}
printf("%d\n", ans);
return 0;
}
- 種類並查集解法
#include<cstdio>
using namespace std;
const int maxn = 50004;
int root[maxn * 3];
int r, x, y, n, k;
int Find(int x) {
return x == root[x] ? x : root[x] = Find(root[x]);
}
bool check(int r, int x, int y) {
if (x > n || y > n || (r == 1 && x == y))
return false;
int fx = Find(x), fy = Find(y);
int fyn = Find(y + n);
int fynn = Find(y + n + n);
if (r == 0) {//同類
if (fx == fyn || fx == fynn)
return false;
}
else {//x喫y
if (fx == fy || fx == fynn)
return false;
}
return true;
}
void Union(int r, int x, int y) {
int fx = Find(x), fy = Find(y);
int fxn = Find(x + n), fyn = Find(y + n);
int fxnn = Find(x + n + n), fynn = Find(y + n + n);
if (fx != fy) {
if (r == 0) {//x和y是同類
root[fx] = fy; //x的同類和y的同類是同類
root[fxn] = fyn; //x的天敵和y的天敵是同類
root[fxnn] = fynn;//x的獵物和y的獵物是同類
}
else {//x喫y
root[fx] = fyn; //x的同類和y的天敵是同類
root[fxn] = fynn;//x的天敵和y的獵物是同類
root[fxnn] = fy; //x的獵物和y的同類是同類
}
}
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 0; i <= 3 * n; i++)root[i] = i;//初始化
int ans = 0;
while (k--) {
scanf("%d%d%d", &r, &x, &y);
r--;
if (check(r, x, y))
Union(r, x, y);
else ans++;
}
printf("%d\n", ans);
return 0;
}
小結
- 如果關係(種類)過多,那麼種類並查集寫起來很繁瑣,帶權並查集更佔優勢。
- 種類並查集還需要足夠的空間,對空間限制小的題也是硬傷。
- 但是種類並查集更容易理解和模擬,帶權並查集需要充分理解其向量思維。
你的點贊將會是我最大的動力