UVA - 11987 Almost Union-Find (并查集可删除)(两种方法)

Almost Union-Find

Time Limit: 1000MS Memory Limit: Unknown 64bit IO Format: %lld & %llu

 Status

Description

Download as PDF

Problem A

Almost 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 7
1 1 2
2 3 4
1 3 5
3 4
2 4 1
3 4
3 3

Output for the Sample Input

3 12
3 7
2 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!


这个题目的一操作也不难,但是二操作如果树的顶端被拿走了,岂不是很尴尬。

先说一个大家的普遍做法:

我们再弄一个数组,专门作为数字的代理人,让这个代理人去执行各种操作

当我们合并两个集合,就把他们代理人的父亲合并就可以了

当我们要把一个元素拿到另一个集合当中时,就把他的代理人换掉(这就是精妙之处),然后把新代理人连接到另一个集合当中

查询操作需要我们为每一个代理人配备一个小本,记录总个数和总和数

代码里细讲某些点:

#include<cstdio>
#include<iostream>
#include<algorithm> 
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int maxn = 1e6;

typedef struct 
{
	int num;//总个数
	int sum;//总和数
} node;

int id[maxn];//代理人
int pre[maxn];//代理人的头头
node me[maxn];//为代理人配备的小本本
int n,m,dep;

void init(int n)//初始化(重要)
{
	for(int i = 0;i<= n;i++)
	{
		id[i] = pre[i] = me[i].sum = i;
		me[i].num = 1;
	}
	dep = n;
}

int find(int x)//寻找头头并压缩路径
{
	int r = x;
	while(r!= pre[r])
		r = pre[r];
	while(pre[x]!= r)
	{
		int temp = pre[x];
		pre[x] = r;
		x = temp;
	}
	return r;
}

int mix(int x,int y)
{
	int fx = find(id[x]);//寻找代理人的头头
	int fy = find(id[y]);
	if(fx!= fy)
	{
		pre[fx] = fy;
		me[fy].num+= me[fx].num;//修改小本本里的信息
		me[fy].sum+= me[fx].sum;
	}
}

void move(int x)//更换代理人并删除原来代理人的头头记录的信息
{
	int fx = find(id[x]);
	me[fx].num--;
	me[fx].sum-= x;
	id[x] = ++dep;
	me[id[x]].num = 1;
	me[id[x]].sum = x;
	pre[id[x]] = id[x];
}

int main()
{
	while(~scanf("%d %d",&n,&m))
	{
		init(n);
		int o;
		int a,b;
		while(m--)
		{
			scanf("%d",&o);
			if(o == 1)
			{
				scanf("%d %d",&a,&b);
				mix(a,b);
			}
			else if(o == 2)
			{
				scanf("%d %d",&a,&b);
				if(find(id[a]) == find(id[b]))//不加这个会wa.比较奇怪
					continue; 
				move(a);
				mix(a,b);
			}
			else
			{
				scanf("%d",&a);
				printf("%d %d\n",me[find(id[a])].num,me[find(id[a])].sum);
			 } 
		}
	}
	
	return 0;
}


这样做其实就蛮好的了,这里分享一下我自己当时的另一个做法(当然可以AC),有兴趣看一下。

让集合元素手牵手,每个元素都知道自己左边是谁,也知道自己右边是谁,还知道自己的老大是谁。

这里的老大实际上是另外的不同类型的元素(老大必须要有所不同嘛),老大保存着小弟们的个数和总和。

①1 p q,就是让p的右手(或左手)松开和让p的右边小弟的左手(或右手)松开,q同理,就能连起来了,并且把q所在集合的所有小弟的老大都改为p的老大。(这样不会超时的,因为集合是越合并越大的,虽然每次要修改所有的小弟,但是我们把小的集合往大的集合里面合并,合并不了多少次就够10万了)

②2 p q,只需要把p的老大的持有的数据修改一下,再把p加入到q的团队,把q的老大持有的数据修改一下就好了

③3 p ,直接读取老大存有的数据就行


代码里细节细讲:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;


typedef struct //小弟
{
	int l;//左朋友
	int r;//右朋友
	int p;//老大
} node1;

typedef struct //老大
{
	int num;//个数
	int sum;//总和
} node2;

node1 a[100005];
node2 b[100005];

void init(int n)//初始化
{
	for(int i = 1;i<= n;i++)
	{
		a[i].l = a[i].p = a[i].r = i;
		b[i].num = 1;
		b[i].sum = i;
	}
}

int main()
{
	int n,m;
	while(~scanf("%d %d",&n,&m))
	{
		init(n);
		int o,x,y;
		while(m--)
		{
			scanf("%d",&o);
			if(o == 1)
			{
				scanf("%d %d",&x,&y);
				if(a[x].p == a[y].p)
					continue;
				if(b[a[x].p].num> b[a[y].p].num)//把小的往大的里面合并
				{
					int t = y;
					b[a[x].p].num+= b[a[y].p].num; //老大信息修改
					b[a[x].p].sum+= b[a[y].p].sum; 
					a[y].p = a[x].p;
					while(a[t].r!= y)//修改被合并元素的老大
					{
						a[a[t].r].p = a[x].p;
						t = a[t].r;
					}
				}
				else
				{
					int t = x;
					b[a[y].p].num+= b[a[x].p].num; 
					b[a[y].p].sum+= b[a[x].p].sum; 
					a[x].p = a[y].p;
					while(a[t].r!= x)
					{
						a[a[t].r].p = a[y].p;
						t = a[t].r;
					}
				}
				int tr = a[x].r,tl = a[y].l;//手牵手的过程细想就能明白
				a[x].r = y;
				a[y].l = x;
				a[tr].l = tl;
				a[tl].r = tr;
			}
			else if(o == 2)
			{
				scanf("%d %d",&x,&y);
				if(a[x].p == a[y].p)
					continue;
				int temp = a[x].l;
				a[a[x].l].r = a[x].r;
				a[a[x].r].l = temp;
				b[a[x].p].num--;
				b[a[x].p].sum-= x;
				temp = a[y].l;
				a[y].l = x;
				a[x].r = y;
				a[temp].r = x;
				a[x].l = temp;
				
				a[x].p = a[temp].p;
				
				b[a[temp].p].num++;
				b[a[temp].p].sum+= x;
			}
			else
			{
				scanf("%d",&x);
				printf("%d %d\n",b[a[x].p].num,b[a[x].p].sum);

			}
		}
	}
	return 0;
}


还有一篇跟这个题类似的题目,做法不同,大家可以看下。Restructuring Company

ACM好题心得





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值