杭电1213 How many tables

How Many Tables

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 24443    Accepted Submission(s): 12233


Problem 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
 

Author
Ignatius.L
/*此题主要思想是找到不同的根节点的个数,涉及到的算法是并查集,图论数的思想。只要找到不同的根节点并对其++便可求出所要的桌子数量,详细步骤见下面代码注释,值得注意的是,此题有两种方法,一种递归,一种递推,递归在数量较为庞大时会超时,此时就因该用递推,但递归较为好写。*/
//第一种递推
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#define maxx 1000 + 10

using namespace std;

int f[maxx];
int  ans;

void mem(){//这里便是将所有节点初始化的过程及初始化为根节点 
    for(int i = 0; i < maxx; i++) f[i] = i;
}

int ffind(int x){//这里是难点
    int root = x;
    while(f[root] != root){//判断当前传来的节点是否为根节点.(条件是自己若与父节点相等便是一个根节点)
        root = f[root];//此处指的是将当前自己的父节点“指向根节点”
    }
    while(x != root){//这里只是判断当前点是否与自己父节点相等由于父节点是目前的“根”,不相等的话现将此时的节点的根节点更新到父节点可以保存当前根节点不一///定是最终根节点但在循环内持续更新将最终更新为根节点
        int tmproot;
        tmproot = f[x];
        f[x] = root;
        x = tmproot;
    }
    return root;//前期返回的不一定是最终的跟可能只是当前某个节点的父节点,而之前是将当前父节点“指向”根的,这个根也许是最终的也可能是当前的父节点//
}

void Union(int x, int y){//此函数作用是将所有有联系的点合并到一个集合内
 int xroot , yroot;
 xroot = ffind(x);
 yroot = ffind(y);
 f[yroot] = xroot;//此处是因为x,是y的父节点因此f[y]=x;这个赋值意思是将x的根节点赋值给y的根节点,即将y的根节点跟新为x的根节点,若x为根节点,此时y的根为x
}

int main()
{
    int T;
    while(scanf("%d", &T) != EOF){
        while(T--){
            int a, b;
            scanf("%d%d", &a, &b);
            ans = 0;
            mem();      //注意这里需将每一个点置为根节点为之后判断当前节点的父节点是否为根节点作条件,因为当前点的父节点和自己相等时,该节点就为根节点。
            for(int i = 0; i < b; i++){
                int c, d;
                scanf("%d%d", &c, &d);
                Union(c, d);
            }
            for(int i = 1; i <= a; i++){
                if(i == f[i]) 
<span style="white-space:pre">		</span>ans++;//这里++的原因为若当前节点与父节点相等,所以此节点为一个根节点,且随着i++每个根节点是不同的,因此直接对ans++不必考虑是否根节点相同。
            }
            cout<<ans<<endl;
        }
    }
    return 0;
}
//第二种:递归法 区别在于findroot()函数,其他相同。但递归好写坏处是,有些请款会超时。(这里我只注释findroot()函数的不同)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#define maxx 1000+10

using namespace std;

int f[maxx];
int ans;

int findroot(int x){//此处较递推不同的原因是 我们写出了他的递归式
    if(x == f[x]) return x;//这里只需判断传来的当前节点是否为根节点若是,则返回一个根节点,若不是递归
    else
        return(findroot(f[x]));
}

void Union(int x, int y){
    int xroot, yroot;
    xroot = findroot(x);
    yroot = findroot(y);
    f[yroot] = xroot;
}

void mem(){
    for(int i = 0; i < maxx; i++) f[i] = i;
}

int main()
{
    int T;
    while(scanf("%d", &T) != EOF){
        while(T--){
            int a, b;
        scanf("%d%d", &a,&b);
        int c, d;
        ans = 0;
        mem();
        for(int i = 0; i < b; i++){
            scanf("%d%d", &c, &d);
            Union(c, d);
        }
        for(int i = 1; i <= a; i++)
            if(f[i] == i)ans++;
        cout<<ans<<endl;
        }
    }
    return 0;
}
//总结一下两种方法:两种方法一样,但主要思路便是我在一代码中注释的那样,在你懂一代码的思想后二代代码和一代码是一样的递推只是将递归展开了而已,且记住一个关键点若当前节点的父节点当前节点相同那么该节点便是一个根节点。这也是此题的核心。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值