入秋以来,困意绵绵,特此刷题,清醒一下。
题目如下:
Bytedance Efficiency Engineering 团队在8月20日搬入了学清嘉大厦,为庆祝团队的乔迁之喜,字节军决定邀请EE团队,举办一个大型交友会。每每题目说道这里肯定有一个问题,那么问题如下:
EE团队一共有N个人,大家都比较害羞,不善于和陌生人交流,这N个人都向字节君提供了自己认识人的名字,但不包括自己(我想是因为一个人可能一生都没有办法清醒的认识到自己吧)。如果A的名单里有B,或者B的吗名单里有A,则代表A和B是互相认识的。同时,如果A认识B,B认识C,则代表A,C也会很快认识,毕竟是通过B这个小媒婆介绍的,两人就可以很快互相认识了。
字节君打算将整个团队分成M组,每组内人员全都可以通过间接或者直接的方式互相认识,而组间的人员互相都不认识。
请您确定一个方案,确定m的最小值。
输入描述:
第一行一个整数n,代表这个团队一共有n个人,从1开始编号。
接下来有n行,第x+1行代表编号为x的人认识的人的编号 k (1<=k<=n),每个人的名单以0代表结束。输出描述:
一个整数m,代表可以分割的最小的组的个数。输入实例:
10
0
5 3 0
8 4 0
9 0
9 0
3 0
0
7 9 0
0
9 7 0
输出:
2
一开始看这题,以为是要求图的联通分量,又要开辟空间,效率很定不高。所以改为用并查集来做这件事情,因为并查集可以很好的解决这种相亲交友,道路修建问题。
贴一下百度百科关于并查集的定义
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合 合并,其间要反复查找一个元素在哪个集合中。并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
下面说一说并查集的两个最重要的操作, 并和 查
一、查找操作:
//将此团队中的每个人进行编号,id[x]存储了标号为x的人它的祖先,例如,id[3]=6,则说明,编号为3的这个人,它的祖先的编号为6,类似于树的存储结构。
//如果,这个人他很孤单,它不认识任何人,那么他的祖先就是他自己,即id[3]=3
int Find(int x) // x为某个人的编号
{
while (x!=id[x]) //他不是一个人,所以他是一定有祖先的
{
id[x]=id[id[x]];//路径压缩 类似于两边之和大于第三边,用第三条边来代替另外两条边,起到路径压缩的作用,同时又不影响找祖先,这是我觉得最简便的写法了,
x=id[x];
}
return x; //最终转换到根节点
}
二、合并操作:
void Union(int p,int q) //找 p,q两个人的祖先,看谁包含的元素多,就将另一个根节点挂到这个元素多的根节点上。也就是弱者被吞并了。
{
//sz[p]存储的是以p为根节点的子树的节点数。
int fp,fq;
fp=Find(p);
fq=Find(q);
if (sz[fp]>=sz[fq])//节点数小的树连接到节点数大的树上
{
id[fq]=fp;
sz[fp]+=sz[fq];
}
else
{
id[fp]=fq;
sz[fq]+=sz[fp];
}
}
也可以加一个判断连通性的操作:
bool connected(int p,int q)
{
if (Find(p)==Find(q))
{
return true;
}
return false;
}
本题的全部代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N(100001);
int n,m,Q;
int sz[N];//以当前节点为根的子树中节点的个数,即为权重
int id[N];
bool ff[N]= {0};
int Find(int x)
{
while (x!=id[x])
{
id[x]=id[id[x]];//路径压缩
x=id[x];
}
return x;
}
void Union(int p,int q)
{
int fp,fq;
fp=Find(p);
fq=Find(q);
if (sz[fp]>=sz[fq])//节点数小的树连接到节点数大的树上
{
id[fq]=fp;
sz[fp]+=sz[fq];
}
else
{
id[fp]=fq;
sz[fq]+=sz[fp];
}
}
bool connected(int p,int q)
{
if (Find(p)==Find(q))
{
return true;
}
return false;
}
int main()
{
scanf("%d",&n);
for (int i=1; i<=n; i++) //节点的标号从1开始的不是从0开始
{
id[i]=i;
sz[i]=1;
}
for (int i=1; i<=n; i++)
{
int t;
while (scanf("%d",&t))
{
if (t!=0)
{
Union(i,t);
}
else
{
break;
}
}
}
int ans=0;
for (int i=1; i<=n; i++)
{
if(i==id[i]) //i是一个树的根节点
ans++;
}
printf("%d\n",ans);
return 0;
}