在并查集中,我们一般做的操作是,把一个集合加入到另一个集合中,从而在查询时,可以高效的查询两个元素的关系。但是有的时候,我们希望可以进行删除操作,就是把一个
点,从一个集合中删除,但是并不影响跟这个点有关系的其他点之间的关系。如图:
当我们删除2号节点后,2号节点成为一个独立的集合,但是,1,3,4号点仍然在一个集合中,如图:
显然,常规的并查集并不能实现这一操作,因此,我们需要另一种写法。常规并查集的初始化的方法是把每个节点的父节点初始化成自己,fa[u]==u,u这个节点就是整个集合的祖宗,此时,u点就有了两重身份:既是一个节点的名字,也是这个集合祖宗的名字,而删除操作的关键就是只改变这一个点的祖宗,而不能改变这个点所在集合的祖宗。因此要把每个节点名字的两重身份分离开,u只表示这个节点的名字,然后给他找一个祖宗(建立虚点),而且,u永远不会是u的祖宗。先给每一个节点建立一个父节点。
void pre(){ index=n; for(int i=0;i<n;i++){ fa[i]=index++; } for(int i=n;i<n*2;i++){ fa[i]=i; } }
0-n-1表示节点的名字,n-n*2-1表示父节点,t点的父节点是t+n号节点。index用于在删除时不断往后扩展新的节点,例如在删除了2号节点后,2号节点是 一个独立的集合,因此它的父节点不能是原来的任何一个节点中,因此fa[2]=index++;
void del(int u){ fa[u]=index; fa[index]=index++; }
这样做,其他原来跟2同一祖宗的节点的祖宗并没有改变,他们还是连在2号节点原来所连的虚点上,从而实现了删除操作。
例题: 两种操作:M x y将x,y并入一个集合中,S x删除x点,x点成为一个独立的集合,最后求一共有多少个集合。
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; int readin(){ int yi=0; char c=getchar(); while(!isdigit(c)){ c=getchar(); } for(;isdigit(c);c=getchar()){ yi=yi*10+c-'0'; } return yi; } int n,m,fa[1000010],index,dl; void pre(){ index=n; for(int i=0;i<n;i++){ fa[i]=index++; } for(int i=n;i<n*2;i++){ fa[i]=i; } } int find(int u){ if(u==fa[u]) return u; return fa[u]=find(fa[u]); } void del(int u){ fa[u]=index; fa[index]=index++; } void end(){ int sumn=1; for(int i=0;i<n;i++){ int xi=find(i); } sort(fa,fa+n); for(int i=1;i<n;i++){ if(fa[i]!=fa[i-1]) sumn++; } cout<<"Case #"<<dl<<": "; cout<<sumn<<endl; } void work(){ char ci; int xi,yi; for(int i=1;i<=m;i++){ cin>>ci; if(ci=='M'){ xi=readin(); yi=readin(); fa[find(xi)]=find(yi); } else{ xi=readin(); del(xi); } } } int main(){ while(true){ dl++; n=readin(); m=readin(); if(!n) return 0; pre(); work(); end(); } }