http://poj.org/problem?id=1988

看黑书看到并查集,练了一道并查集的题目,很典型,不是很难,但是细节上的处理还是比较容易出错的。

理解题意之后,首先对状态进行建模。分别建立三个数组,意义如下

int father[i];//记录 i 的父亲节点,注意不一定是根节点,路径压缩之后才一定是根节点

int total[i];//记录集合中元素个数,仅当i为根节点该值才有意义

int deep[i];//每棵树距离父亲的距离,注意不是根树,所以每次进行路径压缩压缩的时候,将原父亲改为根节点时不要忘记将深度值也同时进行修改

如果输入为M i j 那么就调用合并函数unin(i,j)

如果输入为C i 那么就首先查处i所在集合的根节点fatherI,在调用find(i)的同时,deep[i]也由于路径压缩,所以记录的一定为i到根节点的距离,所以 i所在集合的总的元素个数(即为total[fatherI])减去(deep[i]+1)即为i后面的元素个数,问题得以求解

还需要提到的一点就是,在find()函数中的路径压缩阶段,比较啰嗦,尤其是对deep[]数组的修改,两个迭代缠在一起,并非同步,看代码即可知道,在每一次循环中,修改father[son]的父亲为root的后,修改的不是deep[son],而是deep[father[son]],自己画画图就清楚了。

代码如下:

#include<iostream>
using namespace std;
const int SIZE = 30005;
int father[SIZE];//记录每棵树的根节点(集合标记值)
int total[SIZE];//记录集合中元素个数,仅根节点该值有意义
int deep[SIZE];//每棵树距离父亲的距离,注意不是根树,所以每次要进行路径压缩
//只有父亲直接指向树根,deep[i]值才是实际到树根的深度
void init(int n){
for(int i=1;i<=n;i++){
father[i]=i;
total[i]=1;
deep[i]=0;
}
}

int find(int i){
int depth =deep[i];
int son = i;
while(i!=father[i]){
i=father[i];
depth+=deep[i];
}//找到根节点为i

//路径压缩,同时进行deep[]数组的修改
int temp,tempLen=deep[son],tempLen2;
deep[son]=depth;//因为每次总是修改父亲的深度,所以现将第一个的深度修改掉
while(son!=father[son]){
temp=father[son];
tempLen2 = deep[temp];//几下父亲最初和“父亲的父亲”的距离,为了实现迭代过程
father[son]=i;//路径压缩
deep[temp]=deep[son]- tempLen;
tempLen=tempLen2;//迭代 实现
son=temp;//迭代 实现
}


return i;
}
void unin(int i,int j){
int fatherI= find(i);
int fatherJ=find(j);
father[fatherJ]=fatherI;//合并
deep[fatherJ]=total[fatherI];//j所在集合要放在i所在集合的下面
total[fatherI]+=total[fatherJ];//两个集合合并之后修改根节点所包含的总的元素个数
return ;
}
int main(){
int P,a,b,root;
cin>>P;
init(SIZE);
char choice;

while(P--){
cin>>choice;
if(choice=='M'){
cin>>a>>b;
unin(a,b);
}
else if(choice=='C'){
cin>>a;
root=find(a);
cout<<total[root]-deep[a]-1<<endl;
}
}
return 0;
}



                         

posted on 2011-10-16 01:26  geeker  阅读(227)  评论(0编辑  收藏  举报