UVA

来源:互联网 发布:js获取div class 编辑:程序博客网 时间:2024/03/29 15:55

Almost Union-Find

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

Submit 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 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!


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

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

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

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

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

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

代码里细讲某些点:

#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好题心得