您的位置:首页 > 其它

uva 11987 Almost Union-Find(并查集的删除操作)

2016-10-09 11:32 435 查看
题意:

初始时,一共有n个元素的组合1,2,3....n

给出三个操作

1 p q:合并p,q所在的集合

2 p q:把p移动到q所在的集合

3 p:输出p所在的集合的元素的个数

思路:1,3相当简单,赤裸裸的并查集就好了 
麻烦的是2,并查集是单向的,只知道父亲不知道儿子 
所有如果i是叶子节点无所谓 
如果是父亲节点就会十分的蛋疼: 
你是到另一个集合里面去了,你的儿子们呢? 
把i这个点复制一下(编号为cnt++),当作叶子节点扔到新集合里去 
原来的i仅仅有作为集合编号的作用,可以认为是个空点 ,只是为了不让原来的并查集出现错误,防止他的儿子们找不到根节点

重新找一个节点,记录这个节点自己的信息,也就是sum = 他自己, num = 1;

注意:合并或者查询等的时候一定要合并id[i],因为原来的i只是一个空节点了,为了保证并查集不出错而存在的,在del函数的时候,传参

要穿本身,因为一会要用到本身,新的节点的sum是他本身,并且对id数组更新的时候,下标也是他本身

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3*1e5+5;
int pre[maxn], id[maxn], num[maxn], sum[maxn], cnt;

void init(int n)
{
cnt = n;
for(int i = 1; i <= n; i++)
pre[i] = i, num[i] = 1, sum[i] = i, id[i] = i;
}

int Find(int x)
{
int r = x;
while(pre[r] != r) r = pre[r];
int i = x, j;
while(pre[i] != r)
{
j = pre[i];
pre[i] = j;
i = j;
}
return r;
}

void join(int x, int y)
{
int a = Find(x);
int b = Find(y);
if(a != b)
{
pre[b] = a;
sum[a] += sum[b];
num[a] += num[b];
}
}

void del(int x)
{
int r = Find(id[x]);
num[r]--;
sum[r] -= x;
cnt++;
pre[cnt] = cnt;
num[cnt] = 1;
sum[cnt] = x;
id[x] = cnt;
}

int main(void)
{
int n, m;
while(cin >> n >> m)
{
init(n);
while(m--)
{
int cmd, x, y;
scanf("%d", &cmd);
if(cmd == 1)
{
scanf("%d%d", &x, &y);
join(id[x], id[y]);
}
if(cmd == 2)
{
scanf("%d%d", &x, &y);
//注意要判断下是否在同一颗树上
if(Find(id[x]) != Find(id[y]))
{
del(x);
join(id[x], id[y]);
}
}
if(cmd == 3)
{
scanf("%d", &x);
int r = Find(id[x]);
printf("%d %d\n", num[r], sum[r]);
}
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  uva 并查集 算法