标签:
问题描述:
第一行为两个整数:n和m分别代表开始集合的个数和命令的个数。
初始化每个集合只有一个元素,从1到n一共n个集合,第i个集合里面的那一个元素值为i。
有三个命令:1命令后面紧跟两个参数p,q代表把p和q所在集合合并。
2命令同样两个参数,假设p,q代表把p单独从自己集合中拿出放到q集合里面。
1命令如果p,q已经在同一个集合里面,则忽略此命令,2命令同理。
3命令为查询命令,后面紧跟一个参数p,查询p所在集合中元素的个数和所有元素之和。
Sample Input
5 7
1 1 2
2 3 4
1 3 5
3 4
2 4 1
3 4
3 3
Sample Output
3 12
3 7
2 8
代码思路:
这道题因为n和m的最大值都为1e5且时间限制为1s,所以套两层大循环会TLE。
这道题会用到一个小技巧:
当2命令时,对根节点进行移动,如果对每一个子节点都进行遍历修改值得话会超时。这个时候,我们大可以不去动他,让他仍然去存储当前集合的信息,
而是用一个n之外的节点去代替它进行移动。当对此节点进行查询的时候可以去查找那个节点(因为如果这个点被移动了之后,就不可能再次成为根节点,所以d[]的值不会改变太多次),所以我们需要一个数组去存储下角标所对应的那个节点(替罪羊)在哪里,代码里面我是用的d[]。
#include<stdio.h> #include<string.h> #include<stdlib.h> #define MAX 100000 int n,m; int pre[2*MAX+7],d[MAX+7],sum[MAX+7],num[MAX+7]; void init() { for(int i=1;i<=MAX;i++) { pre[i]=i; sum[i]=i; num[i]=1; d[i]=i; } } int find(int x) { int r=x; while(r!=pre[r]) r=pre[r]; int i=x,j; while(i!=r) { j=pre[i]; pre[i]=r; i=j; } return r; } void mix(int x,int y,int opera) { int fx=find(d[x]); int fy=find(d[y]); if(fx!=fy) { if(opera==1) { num[fy]+=num[fx]; sum[fy]+=sum[fx]; num[fx]=0; sum[fx]=0; pre[fx]=fy; } else { num[fx]--; sum[fx]-=x; num[fy]++; sum[fy]+=x; d[x]=x+MAX; pre[d[x]]=fy; } } } int main() { while(scanf("%d%d",&n,&m)!=EOF) { init(); for(int i=1;i<=m;i++) { int opera; scanf("%d",&opera); if(opera==1||opera==2) { int x,y; scanf("%d%d",&x,&y); mix(x,y,opera); } else { int x; scanf("%d",&x); int root=find(d[x]); printf("%d %d\n",num[root],sum[root]); } } } return 0; }
UVA 11987 Almost Union-Find(并查集之五)
标签:
原文地址:http://www.cnblogs.com/burning-flame/p/4945943.html