并查集,并查集是一种树形结构,又叫“不相交集合”,保持了一组不相交的动态集合,每个集合通过一个代表来识别,代表即集合中的某个成员,通常选择根做这个代表。
也就是说,并查集是用来处理不相交集合类型问题,如问不相交集合有几个。给定节点,找到该节点所在集合元素个数,当然这只是水题。并查集会与其他算法结合着考,如LCA中的tarjian算法。后续博客会整理。
并查集,顾名思义,主要分三部分。
一:合并:给出两点关系,如果属于同一集合,进行merge
二:查:在合并时,需要先写出查,即找到该点的祖先点
三:集:merge后,将新加入的点的祖先点更新
然后,点集就因为共同的祖先点被分为不同的集合啦
结合例题更容易理解
裸题模板:
hdu1232畅通工程
畅通工程
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 52315 Accepted Submission(s): 27902
Problem Description
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
Input
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。
Output
对每个测试用例,在1行里输出最少还需要建设的道路数目。
Sample Input
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
Sample Output
1
0
2
998
就是说将所有独立的集合连接起来还需要几条路,那只要找到独立集合个数-1就可以啦
#include
int father[1005];
int Find(int x)
{
while(x!=father[x])
x=father[x];
return x;
}
void Combine(int a,int b)
{
int fa=Find(a);
int fb=Find(b);
if(fa!=fb)
{
father[fa]=fb;
}
}
int main()
{
int n,m;
int i;
int a,b;
while(~scanf("%d",&n))
{
if(n==0)
break;
scanf("%d",&m);
int sum=0;
for(i=1;i<=n;i++)
father[i]=i;
for(i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
Combine(a,b);
}
for(i=1;i<=n;i++)
{
if(father[i]==i)
sum++;
}
printf("%d\n",sum-1);
}
return 0;
}
再看一个引申题,poj1611
The Suspects
Time Limit: 1000MS Memory Limit: 20000K
Total Submissions: 37109 Accepted: 17992
Description
Severe acute respiratory syndrome (SARS), an atypical pneumonia of unknown aetiology, was recognized as a global threat in mid-March 2003. To minimize transmission to others, the best strategy is to separate the suspects from others.
In the Not-Spreading-Your-Sickness University (NSYSU), there are many student groups. Students in the same group intercommunicate with each other frequently, and a student may join several groups. To prevent the possible transmissions of SARS, the NSYSU collects the member lists of all student groups, and makes the following rule in their standard operation procedure (SOP).
Once a member in a group is a suspect, all members in the group are suspects.
However, they find that it is not easy to identify all the suspects when a student is recognized as a suspect. Your job is to write a program which finds all the suspects.
Input
The input file contains several cases. Each test case begins with two integers n and m in a line, where n is the number of students, and m is the number of groups. You may assume that 0 < n <= 30000 and 0 <= m <= 500. Every student is numbered by a unique integer between 0 and n−1, and initially student 0 is recognized as a suspect in all the cases. This line is followed by m member lists of the groups, one line per group. Each line begins with an integer k by itself representing the number of members in the group. Following the number of members, there are k integers representing the students in this group. All the integers in a line are separated by at least one space.
A case with n = 0 and m = 0 indicates the end of the input, and need not be processed.
Output
For each case, output the number of suspects in one line.
Sample Input
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
Sample Output
4
1
1
题意是,一些学生被分组,0号可能感染病毒,跟他同一集合的也可能感染,那么给出几个分组,问感染的人数。所以,0作为祖先节点,只要与0同集合就将人数数组增加。
#include
#define MAX 30005
int a[MAX],pre[MAX];
int find(int x)
{
if(x!=pre[x])
//找到其祖先节点
pre[x] = find(pre[x]);
//由父节点继续向上递归查询 ,并将其父节点变成找到的
return pre[x];
}
void merge(int x,int y)
{
//分别查询两点的祖先节点。
int prex = find(x);
int prey = find(y);
//如果二者的祖先节点不一致,那么任意让二者中某一个认另一个为祖先,保证同集合。
if(prex == prey)
{
return ;
}
//应该是祖先节点进行组合。而不是当前节点!
pre[prey] = prex;
a[prex] += a[prey];
}
int main()
{
int n,m;
int k,x,y;
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0)
{
return 0;
}
for(int i=0;i//先将自身作为祖先节点。
pre[i] = i;
a[i] = 1;
}
for(int i=0;i//给出集合每个集合人数,以及第一个人的编号
scanf("%d%d",&k,&x);
k--;
while(k--)
{
scanf("%d",&y);
merge(x,y);
}
}
printf("%d\n",a[find(0)]);
}
return 0;
}
在find函数采用了状态压缩,将每个点的父节点都更新为合并的祖先节点,这样查询速度将更快。
int find(int x)
{
if(x!=pre[x])
//找到其祖先节点,并将其父节点变成找到的祖先节点
pre[x] = find(pre[x]);
//由父节点继续向上递归查询
return pre[x];
}