做了并查集一段时间了。个人觉得利用并查集解题的套路其实很单调。
1、开一个数组记录各个节点的父节点,初始化
2、给出一对关联的数据,查找
3、根据查找结果如果根不属于同一集合则合并
当然,还可以优化。主要是在查找利用递归,使得在回溯时各个节点的父节点都是树的根节点。下次查找就可以
降低查找长度。其次,可以利用一个数组记录每棵树的高度。在合并时将矮树链接到高树上,使得新生成的树尽
量矮。
大家可以看看这两个链接:点击打开链接 和点击打开链接
我做这题的想法:
1、在合并两棵树时一般要求是根节点不相同才合并。其实,我觉得这个要求也是可以根据实际情况来看的。
如果只是比较单纯的并查集问题可以相同也合并,合并前后也没有啥变化。但是比如本题就必须是不同才合并。
相同也合并会对num[]造成影响。
2、注释掉的代码是用来在合并时将矮树链接到高树上用的。可是空间不够了。
大家在看代码时可能会发现这样一个问题,以a为根的树链接到以b为根的树上后,height[a]应该赋值为0。代码中
并没有这样做。因为不处理也不影响。为啥呢?因为a已经不再是根了。即使下次要处理一组关联的边(a,c)时,也
是用a的根和c链接。也就是说a不会再成为根,height[a]也不会再被用了。
AC代码:
#include<iostream> using namespace std; const int MAX=10000001; int father[MAX]; //father[i]存放i的父节点 int num[MAX]; //num[i]以i为根的节点数 //int height[MAX]; //树的高度 void initial() //初始化 { for(int i=0;i<MAX;i++) { father[i]=i; num[i]=1; //每个i都是根节点,节点数都为1 //height[i]=1; } } int find(int a) //查找 { if(a==father[a]) return a; return father[a]=find(father[a]); //路径压缩 } void combine(int a,int b) //不在同一集合就合并 { int tmpa=find(a); int tmpb=find(b); if(tmpa!=tmpb) { father[tmpa]=tmpb; num[tmpb]+=num[tmpa]; num[tmpa]=0; /* if(height[tmpb]>height[tmpa]) //每次合并都使树的高度尽量小 { father[tmpa]=tmpb; num[tmpb]+=num[tmpa]; num[tmpa]=0; } else { father[tmpb]=tmpa; num[tmpa]+=num[tmpb]; num[tmpb]=0; if(height[tmpa]==height[tmpb]) height[tmpa]++; } */ } } int maxNode() //连通分量最大节点数 { int tmp=0; for(int i=0;i<MAX;i++) { if(num[i]>tmp) tmp=num[i]; } return tmp; } void preDeal(int n) //预处理 { int a,b; for(int i=0;i<n;i++) { scanf("%d%d",&a,&b); combine(a,b); } } int main() { int n; while(scanf("%d",&n)==1) { initial(); preDeal(n); printf("%d\n",maxNode()); } return 0; }