【HDU 1213】How many tables(并查集模板)

来源:互联网 发布:淘宝旺旺号有什么用 编辑:程序博客网 时间:2024/06/06 02:23

How many tables


Description
Today is Ignatius’ birthday. He invites a lot of friends. Now it’s dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.

One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.

For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.


Input
The input starts with an integer T(1<=T<=25) which indicate the number of test cases. Then T test cases follow. Each test case starts with two integers N and M(1<=N,M<=1000). N indicates the number of friends, the friends are marked from 1 to N. Then M lines follow. Each line consists of two integers A and B(A!=B), that means friend A and friend B know each other. There will be a blank line between two cases.
Output
For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.


Sample Input
2
5 3
1 2
2 3
4 5

5 1
2 5
Sample Output
2
4


算法初探:

并查集算法第一题,之前找了些并查集入门教程看了看,这篇还不错。(传送门-【荐】并查集入门)
当初学最小生成树时克鲁斯卡尔算法就是并查集的思想,现在算是正式开始入门并查集算法吧。

定义如下:
- 并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
- 并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。 集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。(百度百科)

看完之后还是有一种云里雾里的感觉。并查集就是一种将一组数据中的所有有联系的数据,以树形联系起来(一个元素也可以看成只有根的树),形成一个联通块(学过数据结构的同学对此应该比较有体会),每个元素有父节点(前驱结点),只有一个元素没有父节点,此节点称为根。这样能方便的求得联通块数量。

算法模板:(详细解释参见上面的传送门)

#include<iostream>#include<cstring>using namespace std;#define MAX 1005int pre[MAX];int Find(int x){    //寻找根结点     int r=x;//让r帮x去找     while(r!=pre[r])    {        r=pre[r];    }    //路径压缩     int i=x,j;//j是临时变量,存i的前驱     while(i!=r)    {        j=pre[i];        pre[i]=r;//i的前驱变为根结点        i=j;//将i的上级(非根)依次赋给i完成上述动作(前驱变为根结点) //即全都拜在根门下     }    //返回根     return r;}int join(int x,int y)   //判断x y是否连通 {    int Fx=Find(x),Fy=Find(y);//分别找x,y的根结点     if(Fx!=Fy)   //根结点不同,即属于不同的联通块       pre[Fx]=Fy;// pre[Fy]=Fx;也可,没有区别。在两不同联通块之间连线 }int main(){    ······} 

题意:

N人参加酒会。酒桌上陌生人很尴尬,因此只能朋友坐一张桌子。若AB是朋友,BC是朋友,则AC也是朋友,则ABC可以坐一张桌子。求问最少需要的桌子数。
数字T,表示数据组数。接下来T组数据:N表示人数,M表示关系数(谁和谁是朋友)输出最少需要的桌子数。

思路:

这道题很明显可以直接套用并查集模板。pre[]数组记得初始化(pre[i]=i;)。
将每个点加入(join),形成ans个联通块。
初始化记录数组t[]为0,ans为0。
遍历每一个点,搜索到其祖先(树的根),将记录数组中其祖先位置改为1。
之后遍历t[]数组,为1时ans++;即求出联通(朋友之间互相认识)块的数量,即为题目所需,桌子的数量。


代码示例:

#include<iostream>#include<cstring>using namespace std;#define MAX 1005int pre[MAX];int Find(int x){    //寻找根结点     int r=x;//让r帮x去找     while(r!=pre[r])    {        r=pre[r];    }    //路径压缩     int i=x,j;//j是临时变量,存i的前驱     while(i!=r)    {        j=pre[i];        pre[i]=r;//i的前驱变为根结点        i=j;//将i的上级(非根)依次赋给i完成上述动作(前驱变为根结点) //即全都拜在根门下     }    //返回根     return r;}int join(int x,int y)   //判断x y是否连通 {    int Fx=Find(x),Fy=Find(y);//分别找x,y的根结点     if(Fx!=Fy)   //根结点不同,即属于不同的联通块       pre[Fx]=Fy;// pre[Fy]=Fx;也可,没有区别。在两不同联通块之间连线 }int main(){    int N;    while(scanf("%d",&N)!=EOF&&N)    {        while(N--)        {            int SUM,n;            scanf("%d %d",&SUM,&n);            for(int i=1;i<=SUM;i++) pre[i]=i;            while(n--)            {                int a,b;                scanf("%d %d",&a,&b);                join(a,b);             }            bool t[MAX]={0};            int ans=0;            for(int i=1;i<=SUM;i++)                 t[Find(i)]=1;            for(int i=1;i<=SUM;i++)                 if(t[i]==1)                    ans++;            printf("%d\n",ans);    }    }    return 0; } 

注:此题与HDU 1232畅通工程几乎一模一样,只有输入数据的方式稍微不一样和输出不一样。
区别:畅通工程需要输出联通所有联通块的最小道路数,此数目很容易知道为 ‘联通块数-1’ (即ans-1)