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; }
我爱学习,学习使我快乐