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],称为集合的代表元.
◆并查集支持三种操作:
Init(X): 集合初始化:把元素xi加到集合Si中。每个集合Si只有一个独立的元素xi,并且元素xi就是集合Si的代表元素。
Find(x): 查找:查找xi所在集合Si的代表root[Si]。
优化:路径压缩。
Union(x, y): 合并:把x和y所在的两个不同集合合并。
并查集的一个重要的应用是确定给定集合上的等价关系的个数。
等价关系是一个具有自反、对称和传递三个性质的关系。
等号“=”在实数集合R上是一个等价关系。
对于实数中的任意x、y、z。一定满足下列关系:
1)、x=x (自反性)
2)、如果x=y,则y=x (对称性)
3)、如果x=y,y=z,则x=z (传递性)
三种操作:
►Init(X): 集合初始化:
father[xi]= xi(或者0); 每个结点都是一颗独立的树,
是该树的代表元素。
►Find(x): 查找:
查找x所在集合Si的代表root[Si]。
即:查找x所在树的树根结点(代表元素)。
顺着x往上找,直到找到根节点,也就确定了x所在的集合。
►Union(x, y): 合并x和y所在的不同集合。
p=find(x);q=find(y);
if(p!=q)father[p]=q; 或 father[q]=p
Sol:因为每头牛可能通晓几种语言,于是我们将语言并到牛的下面。例如牛1知道1,2,3这3种语言,则语言1,2,3归到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;
}