并查集——元素的合并与删除
一、问题描述 题目链接
有n个节点(1≤n≤100000),进行如下两种操作:
(1) M a b, 把a、b合并
(2)S a, 把a分离出来
进行M(1≤M≤1000000)次操作,问最后有几个组?
二、解题思路
用并查集来实现,我们都知道并查集的合并操作很容易实现,而从集合中移出一个元素却很难。
这样想,移出元素相当于建立一个新的集合,我们只需将其指向一个独立的节点。这对于叶子节点容易操作,但如果是中间节点(或根节点),则会导致原集合断开。
我们将节点编号为0~n-1,将他们的根节点初始化为n~2n-1,这样保证了节点0~n-1都是叶子节点。同时将所有的移出节点分别指向2n~2n+m。最后查询0~n-1所在的不同的集合数。
三、代码实现
1 #include<stdio.h> 2 #include<iostream> 3 #include<cstring> 4 #include<cstdbool> 5 #include<algorithm> 6 using namespace std; 7 8 const int maxn = 100000 + 10; 9 const int maxk = 1000000 + 10; 10 int n, qcnt; 11 int fa[2 * maxn + maxk]; 12 bool vis[2 * maxn + maxk]; //注意数组范围,2n+最大查询数 13 14 void init() 15 { 16 memset(vis, false, sizeof(vis)); 17 for (int i = 0; i < n; i++) 18 fa[i] = i + n; 19 for (int i = n; i < 2 * n + qcnt; i++) 20 fa[i] = i; 21 } 22 23 int findset(int x) 24 { 25 if (x != fa[x]) 26 return fa[x] = findset(fa[x]); 27 return fa[x]; 28 } 29 30 void unite(int x, int y) 31 { 32 int rx = findset(x); 33 int ry = findset(y); 34 35 fa[rx] = ry; 36 } 37 38 int main() 39 { 40 int kase = 0; 41 while (scanf("%d%d",&n,&qcnt) == 2 && (n || qcnt)) //题目描述有误,可能存在n>0,qcnt = 0的情况 42 { 43 init(); 44 int cur = 2 * n; 45 while (qcnt--) 46 { 47 char order[3]; 48 int a, b; 49 scanf("%s%d", order, &a); 50 if (order[0] == 'M') 51 { 52 scanf("%d", &b); 53 unite(a, b); 54 } 55 if (order[0] == 'S') 56 { 57 fa[a] = cur;cur++; 58 } 59 } 60 int ans = 0; 61 for (int i = 0; i < n; i++) 62 { 63 int root = findset(i); 64 if (!vis[root]) 65 { 66 ans++; 67 vis[root] = true; 68 } 69 } 70 printf("Case #%d: %d\n", ++kase, ans); 71 } 72 }
个性签名:时间会解决一切