受教了,感谢玉斌大神的博客。
这道题最难的地方就是操作2,将一个集合中的一个点单独移到另一个集合,因为并查集的性质,如果该点本身作为root节点的话,怎么保证其他点不受影响。
玉斌大神的思路很厉害,受教受教,即,由于题目最终输出集合的元素个数与权值总和,故添加一个delete操作,将该点(设为P)所在集合的rank和sum值减小,将p的father引向一个从没定义过的点,(可以设置为(总数++)点),这样,虽然看似P还留在原集合,但仅仅作为一个空骨架,并不对集合的rank和sum产生影响。
具体实现,需要借助一个辅助数组 id[], id[]初始和father[]相同,但一旦需要删除操作,即将id[p]=++n,指向一个新位置,下次father[id[x]]即指向了新位置,跟原集合无关了。
#include <iostream> #include <cstdio> #include <cstring> using namespace std; int father[200005]; //由于最坏可能有1000000次删除操作,故最大的数组量 int rank[200005]; long long sum[200005]; int id[200005]; int n; int cnt; void init() { for(int i=1;i<=n;i++) { father[i]=i; rank[i]=1; sum[i]=i; id[i]=i; } cnt=n; } int findset(int x) { if (x!=father[x]) { father[x]=findset(father[x]); } return father[x]; } void unionset(int x,int y) { x=id[x]; y=id[y]; //所有点的指向,全部通过一个间接的id[x]的值来指向相应的值 即将所有点全部id化,不再已原来的本身值为id。 int r1=findset(x); int r2=findset(y); if (r1==r2) return; if (rank[r1]>rank[r2]) { father[r2]=r1; rank[r1]+=rank[r2]; sum[r1]+=sum[r2]; } else { father[r1]=r2; rank[r2]+=rank[r1]; sum[r2]+=sum[r1]; } } void q1() { int p,q; scanf("%d %d",&p,&q); unionset(p,q); } void dele(int x) { int r=findset(id[x]); rank[r]--; sum[r]-=x; id[x]=++cnt; father[id[x]]=id[x]; rank[id[x]]=1; sum[id[x]]=x; } void q2() { int p,q; scanf("%d %d",&p,&q); int r1=findset(id[p]); int r2=findset(id[q]); if (r1==r2) return; dele(p); unionset(p,q); } void q3() { int p; scanf("%d",&p); int root=findset(id[p]); printf("%d %lld ",rank[root],sum[root]); } int main() { int q; while (scanf("%d %d",&n,&q)!=EOF) { init(); int i,j,k; for (i=1;i<=q;i++) { int deno; scanf("%d",&deno); if (deno==1) q1(); if (deno==2) q2(); if (deno==3) q3(); } } return 0; }