HDU 1213 How Many Tables

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

这是一道简单的并查集题目,他邀请一堆人过生日,现在要安排桌子,如果A认识B,B认识C,那么A也认识C,然后把他们安排到一张桌子上去,显然这是并查集,需要处理不相交子集的问题。

所以在这里我简单写一下并查集的相关内容

并查集算是一种数据结构,专门用来处理不相交集合的问题

首先,定义一个root数组,初始化为root[i] = i的形式,这个代表的意思就是每一个元素的当前根元素就是自己本身。

然后是一个查找函数find,传入一个数字,查找这个数字的根

int find(int x)
{
	return x == root[x] ? root[x] : find(root[x]);
}

这个代码的原理很简单,首先,如果当前元素的根节点就是本身,那没什么好说的,直接返回root[x],如果不是本身的话,那么就返回find(root[x]),进入一个递归的过程。

这样写的话比较简单,但是对于数据量比较大的时候,这样每一次都需要重复的查找,效率很低,所以就出现了 压缩路径法,上代码

int find(int x)
{
	if(x != root[x])root[x] = find(root[x]);
	return root[x];
}

上面代码的原理是啥呢?首先我判断一下x是否等于root[x],如果相等,那么直接返回root[x],如果不想等,那么我就让root[x]等于最后的那个根节点的值,最后在return root[x],完美!!!那么有没有非递归的写法呢?当然是有的

int find(int x)
{
	int r = x;
	while(r != root[r])r = root[r];
	int t;
	while(x != r)
	{
		t = root[x];
		root[x] = r;   //这一步的目的是把root[x]设置为根节点
		x = t;
	}
}

以上就是find的非递归写法,还算是好理解吧。

当你写完查找函数之后,下一步就是写合并函数了

void join(int x ,int y)
{
	int rx = find(x);
	int ry = find(y);
	root[rx] = ry;
}

这样呢,就是合并路径的算法,咱们来看一下原理,首先 rx = find(x), ry = find(y);这是什么意思呢?首先我定义两个变量 rx 和 ry 分别保存 x 和 y 的根节点,然后我把 x 的根节点的根设置为 y 的根节点,这就是一种合并操作,现在x 和 y 就是一个根节点了,或者说 x 的根节点变成了 y 的根节点了。

下面贴一下这个题目的完整代码:

#include <iostream>
using namespace std;

const int MAX = 1000000;
int root[MAX];

int find(int x)
{
	if(x != root[x])root[x] = find(root[x]);
	return root[x];
}


void join(int x,int y)
{
	int rx = find(x);
	int ry = find(y);
	if(rx != ry)root[rx] = ry;
}
int main()
{
	int T;
	cin >> T;
	while(T--)
	{
		int n,m;
		cin >> n >> m;
		for(int i = 0;i <= n;i++)
		{
			root[i] = i;
		}
		for(int i = 0;i < m;i++)
		{
			int a,b;
			cin >> a >> b;
			join(a,b);
		}
		int count = 0;
		for(int i = 1;i <= n;i++)
		{
			if(root[i] == i)count++;
		}
		cout << count << endl;
	}
	return 0;
}

因为这个题目比较有代表性,所以并查集的题目就选这个了。

我的另一个博客里面有相关题型的链接

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值