做了并查集一段时间了。个人觉得利用并查集解题的套路其实很单调。
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;
}