Problem AAlmost Union-Find
I hope you know the beautiful Union-Find structure. In this problem, you're to implement something similar, but not identical.
The data structure you need to write is also a collection of disjoint sets, supporting 3 operations:
1 p q
Union the sets containing p and q. If p and q are already in the same set, ignore this command.
2 p q
Move p to the set containing q. If p and q are already in the same set, ignore this command
3 p
Return the number of elements and the sum of elements in the set containing p.
Initially, the collection contains n sets: {1}, {2}, {3}, ..., {n}.
Input
There are several test cases. Each test case begins with a line containing two integers n and m (1<=n,m<=100,000), the number of integers, and the number of commands. Each of the next m lines contains a command. For every operation, 1<=p,q<=n. The input
is terminated by end-of-file (EOF). The size of input file does not exceed 5MB.
Output
For each type-3 command, output 2 integers: the number of elements and the sum of elements.
Sample Input
5 71 1 22 3 41 3 53 42 4 13 43 3
Output for the Sample Input
3 123 72 8
Explanation
Initially: {1}, {2}, {3}, {4}, {5}
Collection after operation 1 1 2: {1,2}, {3}, {4}, {5}
Collection after operation 2 3 4: {1,2}, {3,4}, {5} (we omit the empty set that is produced when taking out 3 from {3})
Collection after operation 1 3 5: {1,2}, {3,4,5}
Collection after operation 2 4 1: {1,2,4}, {3,5}
Rujia Liu's Present 3: A Data Structure Contest Celebrating the 100th Anniversary of Tsinghua University
Special Thanks: Yiming Li
Note: Please make sure to test your program with the gift I/O files before submitting!
這題是受luyuncheng的啟發做出來的。
本題的痛點就在2操作:
要把一個元素從一個集合移到另一個集合。
我們知道並查集是不能刪除元素的,因為整個結構是單向的,不知道兒子是什麼
但這裡移動元素,實際上就蘊含著刪除的操作。那該怎麼辦呢?
有一個想法可能會很快想到,並不實際刪除要移動的元素p,即不改變p集合原來的路徑,但集合的權值還是要改變(相當於把p看成一個虛點),然後把p直接“掛到”q的集合中去。
注意,這裡一定要保證p在以後永遠只能作為一個葉子,不能再做父親,否則會覆蓋掉最初(作為父親時)的路徑,並且可以證明只做葉子的做法一定存在。
這樣在寫程式時要注意的就有以下幾點:
1.讓每個元素的值有二維狀態,一維用於儲存,這個元素在最初集合時的情況,另一維儲存這個元素現在實際的情況
2.在合并時注意權值的改變,應該用哪一維狀態。
3.在find()時先判斷這個元素是否真的存在過去和現在兩個狀態,從而選擇搜尋的路徑不同,並且在寫路徑壓縮時也有些不同。
詳見代碼:
#include <cstdio>struct Node{ int fa[2],num[2],value[2];}ele[100005];void init(){ for(int i = 0;i < 100005;i++){ ele[i].fa[0] = i; ele[i].fa[1] = -1; ele[i].num[0] = 1; ele[i].num[1] = 0; ele[i].value[0] = i; ele[i].value[1] = 0; }}int find(int n){ if(ele[n].fa[1] == -1){ int r = ele[n].fa[0]; while(ele[r].fa[0] != r) r = ele[r].fa[0]; int f = ele[n].fa[0]; while(f != r){ int tem = ele[f].fa[0]; ele[f].fa[0] = r; f = tem; } return r; } else{ int f = ele[n].fa[1]; while(ele[f].fa[0] != f){ f = ele[f].fa[0]; } ele[n].fa[1] = f; return f; }}int n,m;int main(){ while(scanf("%d%d",&n,&m) != EOF){ init(); while(m--){ int ope,p,q; scanf("%d",&ope); if(ope == 1){ scanf("%d%d",&p,&q); int f1 = find(p); int f2 = find(q); if(f1 != f2){ ele[f1].fa[0] = f2; ele[f2].num[0] += ele[f1].num[0]; ele[f2].value[0] += ele[f1].value[0]; } } else if(ope == 2){ scanf("%d%d",&p,&q); int f1 = find(p); int f2 = find(q);//忘了加if(f1 != f2),慢了一些 ele[f1].num[0]--; ele[f1].value[0] -= p; ele[p].fa[1] = f2; ele[f2].num[0]++; ele[f2].value[0] += p; } else{ scanf("%d",&p); int f = find(p); printf("%d %d\n",ele[f].num[0],ele[f].value[0]); } } } return 0;}