国际班的学生(并查集)

Description

某校国际班有N(2 <= N<=10,000)个学生,编号为1.. N, 一共会流利地使用M(1<= M <=30,000)种语言,编号从1 .. M.,第i个学生,会说K_i(1 <= K_i<= M)种语言,即L_i1, Li2,..., L{iK_i} (1 <= L_ij <= M)。 现在如何任意两个学生想要交流的话,除非他们会同一种语言 或者他们能找到某个学生来做翻译。 现在校长希望任两个学生都能互相交流的话,就必须买一些书来。让某些学生来学习新的语言。帮助他确定:*他必须购买的书籍的最低数量

Format

Input

第1行:两个用空格隔开的整数:N和M 第2..N+1行:第i+1行描述的学生i的语言, K_i+1个空格隔开的整数:K_iL_i1 L_i2,...,L_I{K_i}。

Output

第1行:一个整数,校长最少需要购买的书籍数量

Samples

输入数据 1

3 3
2 3 2
1 2
1 1

输出数据 1

1

Hint

给三号学生买第二本书即可

做这个题需了解一下并查集:

    需要将n个不同的元素划分成一组不相交的集合。开始时,每个元素自己成一个单元素集合,然后按照一定的顺序或问题给定的条件和要求将属于同一组元素(有特定关系)所在的集合合并,最后统计集合的个数往往就是问题的解。

    在这个过程中要反复的用到查询某个元素属于哪个集合的运算;两个不同集合合并的运算。适合描述这类问题的抽象数据结构类型称为并查集(合并与查找)。

并查集是一种树型的数据结构,用于处理一些不相交集合S={S1, S2, ,Sn}, 每个集合Si都有一个特殊元素root[Si],称为集合的代表元.

并查集支持三种操作:

InitX: 集合初始化:把元素xi加到集合Si中。每个集合Si只有一个独立的元素xi,并且元素xi就是集合Si的代表元素。

Find(x): 查找:查找xi所在集合Si的代表root[Si]

               优化:路径压缩。

Union(x, y): 合并:把x和y所在的两个不同集合合并。

    并查集的一个重要的应用是确定给定集合上的等价关系的个数。

    等价关系是一个具有自反、对称和传递三个性质的关系。

    等号“=”在实数集合R上是一个等价关系。

        对于实数中的任意xyz。一定满足下列关系:

   1)、x=x (自反性)

   2)、如果x=y,则y=x (对称性)

   3)、如果x=yy=z,则x=z (传递性)

三种操作:

InitX: 集合初始化:

       father[xi]= xi(或者0);   每个结点都是一颗独立的树,
       是该树的代表元素。

Find(x): 查找:

                查找x所在集合Si的代表root[Si]

       即:查找x所在树的树根结点(代表元素)。
       顺着
x往上找,直到找到根节点,也就确定了x所在的集合。

Union(x, y): 合并xy所在的不同集合。

                p=find(x);q=find(y);

                if(p!=q)father[p]=q;  或  father[q]=p

Sol:因为每头牛可能通晓几种语言,于是我们将语言并到牛的下面。例如牛1知道1233种语言,则语言123归到1号牛下面。于是发现编号可能出现混淆。于是我们将语言重新编号为N+1,N+2,N+3.接下面2号牛如果能说2号语言,则也可归到1号牛那个部落了。于是最终只要知道牛被分成了几个部落,设为ans1,于是最终的结果为ans1-1。例如3个部落,只要再加2条边就可以进行合并了。这个题已粗略的引入了“多开点”的概念出来了。

Sol2:当然也可以将牛归到某种语言下面。于是找每头牛,看它归在哪个语言下面。于是可能好几牛在同一个集合中时,他们的父亲点是一样的。于是加个逻辑值判断一下就好了。

代码:

#include<bits/stdc++.h>
using namespace std;
int a[40010];
int n,m,ans,x,y,p,q;
int find(int x) {
	if(a[x]!=x) a[x]=find(a[x]);
	return a[x];
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n+m; i++) a[i]=i;
	for(int i=1; i<=n; i++) {
		int sum;
		cin>>sum;
		for(int j=1; j<=sum; j++) {
			cin>>x;
			p=find(i),q=find(n+x);
			if(p!=q) {
				if(q<=n) a[p]=q;
				else a[q]=p;
			}
		}
	}
	ans=0;
	for(int i=1; i<=n; i++)
		if(a[i]==i) ans++;
	printf("%d\n",ans-1);

	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值