UVA - 11987 Almost Union-Find

题面

 

    感觉这个题还是蛮灵活的,并没有死考一个并查集。

    考虑如果没有2操作,那么这就是一个并查集的模板题。多出一个2操作增加了什么困难呢?

    如果我们直接用并查集维护,那么2操作必须改变f[p],这样的话,原来那些在p子树里的元素也就跟着一起被移到q所在集合里了。

    这显然是不对的。。。

    其实我们要做的仅仅是把p这个点扣出来,而尽量使p原来所在的集合不太变动。

    这么一想,就有了一个思路:每次进行2操作我们就新开一个点代表 新p,我们只需要把 f[新p] 设置成q的根,然后把原根的信息改一改,q的根的信息改一改就好了。对于1~n我们开一个数组记录它们的新点是多少,不管什么操作我们都用新点。

 

    新算法的正确性在于:每次2操作开一个新点之后,原点就变成了一个仅有指示集合作用的虚点,并且在后续操作中(除了原来指向它的点)不会被用到;并且本题求的值都可以直接记录在并查集的根上,所以操作2后改一改两个根的信息以后就一劳永逸了,不会再用到没改的小树的错误信息。。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100005;

int n,m,f[N*2],dy[N],num[N*2];
ll sum[N*2];

inline int read(){
    int x=0; char ch=getchar();
    for(;!isdigit(ch);ch=getchar());
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x;
}

int getf(int x){ return f[x]==x?x:(f[x]=getf(f[x]));}

inline void solve(){
	for(int i=1;i<=n;i++) f[i]=dy[i]=i,num[i]=1,sum[i]=i;
	
	for(int opt,p,q;m;m--){
		opt=read();
		
		if(opt==3) p=getf(dy[read()]),printf("%d %lld\n",num[p],sum[p]);
		
		else if(opt==1){
			p=getf(dy[read()]),q=getf(dy[read()]);
			if(p!=q) f[p]=q,num[q]+=num[p],sum[q]+=sum[p];
		}
		
		else{
			p=read(),q=getf(dy[read()]);
			int fa=getf(dy[p]);
			if(fa==q) continue;
			
			num[fa]--,sum[fa]-=(ll)p;
			dy[p]=++n,f[n]=q,num[q]++,sum[q]+=(ll)p;
		}
	}
}

int main(){
	while(scanf("%d%d",&n,&m)==2) solve();
	return 0;
}

  

posted @ 2019-07-31 10:51  蒟蒻JHY  阅读(157)  评论(0编辑  收藏  举报