Time Limit: 1000MS | Memory Limit: Unknown | 64bit IO Format: %lld & %llu |
Description
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