题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3138
题目大意:对于1-n这n个数,每个数为一个集合,现在有三种操作:第一种:1 x y 即把x 和 y所在的集合合并;第二种:2 x y 即把x所在的集合中删去x,并把x加入y所在的集合中;第三种:3 x 即输出x所在集合元素的个数和所有元素的和。
解题思路:此题关键点就是集合的删除操作,咱们可以换一个角度思考:首先删除某个元素x的结果是 这个集合中元素个数-1,所有元素的和-x,那直接对x元素所在的集合进行此操作,就让x在这个集合中的贡献为0了,也就相当于删除了x,但是现在又有一个问题:刚才的操作虽然相当于删除了x,但实际上x还在那个集合中,这个时候要把x合并到另外一个集合中,就需要找一个x的”替代品“,使得这个替代品与另外一个集合合并产生同样的效果,我们考虑开一个辅助数组id,id【x】为当前x的编号,如果x被删除,就产生一个新的编号代替当前x的编号。
详见代码:
#include<stdio.h>
#define N 200005
int f[N],rank[N],v[N],id[N];
int n,m,mark;
void init()
{
for(int i=0;i<=n;i++)
{
f[i]=i;
rank[i]=1;
v[i]=i;
id[i]=i;
}
mark=n;//mark必须在这里初始化,就这个纠结老长时间
}
int find(int x)
{
if(x==f[x]) return x;
return f[x]=find(f[x]);
}
void merge(int x,int y)
{
int fx=find(id[x]);//查找最新状态x的父节点
int fy=find(id[y]);
if(fx!=fy)
{
f[fx]=fy;
rank[fy]+=rank[fx];//更新秩
v[fy]+=v[fx];//更新和
}
}
void del(int x,int y)
{
int fx=find(id[x]);
int fy=find(id[y]);
if(fx!=fy)//如果在一个集合中就不用进行操作
{
//消除对原集合的影响,并找一个“替代品”
rank[fx]--;
v[fx]-=x;
id[x]=++mark;
//初始化“替代品”,并进行合并操作,和下面注释掉的等效
f[id[x]]=id[x];
rank[id[x]]=1;
v[id[x]]=x;
merge(x,y);
//因为“替代品”在下面的合并中不为根节点,所以“替代品”需要保留的信息
//只是自身的位置信息,而这个信息在id[x]=++mark已经更新过,故直接计算
//其对所要合并集合的影响即可
/*f[id[x]]=fy;
rank[fy]++;
v[fy]+=x;*/
}
}
int main()
{
int k,x,y;
while(~scanf("%d%d",&n,&m))
{
init();
for(int i=0;i<m;i++)
{
scanf("%d",&k);
if(k==1)
{
scanf("%d%d",&x,&y);
merge(x,y);
}
else if(k==2)
{
scanf("%d%d",&x,&y);
del(x,y);
}
else
{
scanf("%d",&x);
int ans=find(id[x]);
printf("%d %d\n",rank[ans],v[ans]);
}
}
}
return 0;
}