并查集入门题集

畅通工程

某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路? 

INPUT

  • 测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。 

  • 注意:两个城市之间可以有多条道路相通,也就是说
    3 3
    1 2
    1 2
    2 1

  • 这种输入也是合法的

  • 当N为0时,输入结束,该用例不被处理。 

OUTPUT

  • 对每个测试用例,在1行里输出最少还需要建设的道路数目。 

测试样例

4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0

样例输出

1
0
2
998

题意理解:

  • 有n个城市,这n个城市由m条路相连。
  • 给出每条路连接的两个城市。
  • 求还需要几条路才可以将全部的城市连接起来。

解题思路:

这道题是并查集的模板题,有人说这是一道生成树,不过我觉得并查集理解起来好理解一些。

就是将已经联通的城市归为一个集合,取出一个城市作为根节点,输入读完之后就已经将联通的城市归为一类了。只要查出有几类就可以了,换句话说只要查出有几个根就可以了,根的特点是他的前驱是它本身。

代码:

#include<iostream>
#include<stdio.h>
using namespace std;
int cnt = 0;
int pre[1005];

int findpre(int x)        //寻找前驱
{
    int r = x;
    while(pre[r]!=r)
        r = pre[r];
    return r;
}
int join(int x,int y)     //合并节点
{
    int fx = findpre(x);  
    int fy = findpre(y);  
    if(fx!=fy)
    {
        pre[fx] = fy;
    }
    return 0;
}

int pl(int x)             //压缩路径,实际上这道题数据量比较小不需要路径压缩
{
    int r = x;
    while(pre[r]!=r)
        r = pre[r];
    int k = x;
    int j;
    while(k!=r)
    {
        j = pre[k];
        pre[k] = r;
        k = j;
    }
    return 0;
}

int main()
{
    while(1)
    {
        int n,m;
        scanf("%d",&n);
        if(n == 0)
            break;
        scanf("%d",&m);
        for(int i=0;i<=n;i++)
        {
            pre[i] = i;
        }
        for(int i=0;i<m;i++)
        {
            int f,t;
            scanf("%d%d",&f,&t);
            join(f,t);
        }
        for(int i=0;i<=n;i++)
        {
            pl(i);
        }
        for(int i = 1;i<=n;i++)
        {
            if(pre[i]==i)
            {
                cnt++;
            }
        }
        cout<<cnt-1<<endl;
        cnt = 0;

    }
    return 0;
}

How Many Tables

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.

测试样例

2
5 3
1 2
2 3
4 5

5 1
2 5

样例输出

2
4

题意理解:

  • 和畅通工程那道题基本一样,只不过这次说的是几个人在一起dinner,认识的人可以坐到一张桌子上,输入有几个人,谁和谁互相认识,需要求要几张桌子。

解题思路:

也是一道并查集模板题。

在每个认识的人里选出一个代表作为整个集合的根节点,最后查找几个根就有几张桌子。

不多说直接粘代码了。

代码:

#include<iostream>

using namespace std;
int pre[1010];
int findpre(int x)
{
    int r = x;
    while(r!=pre[r])
        r = pre[r];
    return r;
}
int join(int x,int y)
{
    int fx = findpre(x);
    int fy = findpre(y);
    if(fx!=fy)
    {
        pre[fx] = fy;
    }
    return 0;
}
int main()
{
    int t;
    cin>>t;
    for(int i=0;i<t;i++)
    {
        int m,n;
        cin>>m>>n;
        for(int j=1;j<=m;j++)
        {
            pre[j] = j;
        }
        for(int j=0;j<n;j++)
        {
            int f,t;
            cin>>f>>t;
            if(f>t)
                join(f,t);
            else
                join(t,f);
        }
        int cnt = 0;
        for(int j=1;j<=m;j++)
        {
            if(pre[j] == j)
            {
                //cout<<pre[j]<<endl;
                cnt++;
            }

        }
        cout<<cnt<<endl;
    }
    return 0;
}


PID331 / 家族

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

INPUT

  • 第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。

  • 以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有亲戚关系。

  • 接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。

OUTPUT

  • P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

测试样例

6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

样例输出

Yes
Yes
No

题意理解:

  • 给定几个人和几组关系,询问几组数据看是否有关系。

解题思路:

这道题还是一道板子题,直接用并查集就可以求解。解题思路和上面两个题基本一致。

代码:

#include<iostream>

using namespace std;
int pre[5005];
int findpre(int x)
{
    int r = x;
    while(pre[r]!=r)
        r = pre[r];
    return r;
}

int join(int x,int y)
{
    int fx = findpre(x);
    int fy = findpre(y);
    if(fx!=fy)
    {
        pre[fy] = fx;
    }
}

int main()
{
    int n,m,p;
    cin>>n>>m>>p;
    for(int i=1;i<=n;i++)
    {
        pre[i] = i;
    }
    for(int i=0;i<m;i++)
    {
        int x,y;
        cin>>x>>y;
        if(x<y)
            join(x,y);
        else
            join(y,x);
    }
    for(int i=0;i<p;i++)
    {
        int x,y;
        cin>>x>>y;
        if(findpre(x)==findpre(y))
        {
            cout<<"Yes"<<endl;
        }
        else
            cout<<"No"<<endl;
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章