算法与数据结构实验题 9.18 群聊
实验任务
在某聊天软件中,共有n个用户,m个群。如果两个用户A、B之间想要交流的话,只能通过消息链A→user1→user2→…→B进行交流,且必须满足链上相邻的两个用户在同一个群里。也就是说,如果A、B在同一个群里的话,可以直接交流。如果A、B不在同一个群里的话,也可以通过若干个中间人进行交流。
现在给出每个用户已经加入的群列表,问这n个用户总共最少还需要再加多少次群,才能使得任意两个用户之间可以进行交流。
数据输入
输入第一行为两个正整数 n(2≤n≤10000)和 m(1≤m≤100)。
接下来n行,每行第一个整数为k_i (0≤k_i≤m),表示第i个用户所加入的群数量。紧接着k_i个正整数,表示第i个用户加入的群编号(编号从1开始)。
数据输出
输出只有一个整数,表示这n个用户总共最少还需加群的次数。
输入示例
8 7
0
3 1 2 3
1 1
2 5 4
2 6 7
1 3
2 7 4
1 1
输出示例
2
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=2e9+1;
int n,m,k,x,fa[100010],ans,cnt;
vector<int>a[100010];
int find(int x) { return x==fa[x]?x:fa[x]=find(fa[x]); }
void merge(int a,int b) {
int u=find(a),v=find(b);
if(u!=v) fa[u]=v;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++) {
scanf("%d",&k);
if(k==0) cnt++;
while(k--) {
scanf("%d",&x);
a[x].push_back(i);
}
}
for(int i=1;i<=m;i++) {
for(int j=1;j<a[i].size();j++) {
merge(a[i][0],a[i][j]);
}
}
for(int i=1;i<=n;i++) if(find(i)==i) ans++;
if(cnt==n) printf("%d",cnt);
else printf("%d",ans-1);
return 0;
}
思路
题意简要来说,就是有n个元素与m个连通集,若一个人同时在多个连通集内,那么这些连通集也互相连通,最终求连通集的数量。
那么输入这块就很显而易见了:
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i; //并查集初始化
for(int i=1;i<=n;i++) {
scanf("%d",&k);
if(k==0) cnt++; //记下来,后面要考
while(k--) {
scanf("%d",&x);
a[x].push_back(i); //将“i”这一元素放入x群中
}
}
第二个要解决的问题是,怎么样确定有多少个群
可以用现实中的方式想一想,一个群总有个“群主”,对应就是并查集中的根,那么我们将一个群中所有的人“Union”在一起后,最后看看并查集数组中有多少个根,也就是“群主”之后,就可以知道有几个群了
其中,当另一个群聊中也有这个人时,在两个群聊执行全员合并时,这两个群聊相当于就合并为了一个大群聊
for(int i=1;i<=m;i++) { //遍历所有的群
for(int j=1;j<a[i].size();j++) { //遍历每个群里的人
merge(a[i][0],a[i][j]); //同一群中的人合并
}
}
for(int i=1;i<=n;i++) if(find(i)==i) ans++; //找到“群主”时加一
到最后,答案应该是ans-1……吗?
if(cnt==n) printf("%d",cnt);
else printf("%d",ans-1);
这就涉及到我们读入时的操作了。正常情况下,至少有一个群聊存在,若有落单的人或群主存在,只需要一次“加群”操作即可,那么答案也就是群聊总数-1,即ans-1;但是当所有人都没有加入任何群聊时,那么ans在循环后即为人数,很明显此时没有群的存在,也就是每一个人都必须做一次“加群”操作,因此,当我们发现所有人都没加群时,输出即为人数
本题考验了我们对并查集这一数据结构的应用熟练度,以及对于题意的仔细理解