一、概述
根据爱好的活动分类,这题出的有缺陷,我只看题目根本看不出来a和b喜欢活动1,b和c喜欢活动2,那么a,b,c同属于一个圈子。还要上网查才能明白。
总结起来就是,这种将一堆元素按照某种相同的特点分类的题目,都用并查集。如何在这道题上应用,我还想了半天。
二、分析
并查集最重要的就是father数组,一切都是从这个数组开始的,可以说是万恶之源,我原来以为这道题需要一个二维的father数组,没想到不需要。
最简单的并查集中的元素只有一种关系,可以视之为“认识”,最开始每个人只认识自己,因此father数组中第i个元素的值为i。随着不断输入,元素之间互相越来越多的认识,每输入一对关系,就相当于一次合并操作,将两个合并成一个。但是本题不同,本题中,元素有许多不同的关系,那么只有一种的认识就不能概括了,因此需要一个新的数组,dalao。
dalao数组的含义是这样的,下标表示第i个活动,值为第一个喜欢这一活动的人,称之为这个活动的大佬。所有剩下的喜欢这一活动的人都需要抱住大佬的大腿,翻译过来就是都要与大佬进行合并操作。这样一来,一个喜欢多个活动的人就会与多个大佬合并,这样也就满足了题意中的传递关系。
例如三号五号和七号
三号喜欢5、3五号喜欢3七号喜欢6、8、1、5
那么简单起见,3、5的大佬都是三号
对于五号来说,他不能做3的大佬,就只能和3的大佬,也就是三号合并
对于七号来说,他可以做6、8、1的大佬,不能做5的大佬,只能和5的大佬,也就是三号合并
这样三号、五号、七号就是一个社交集合了
接下来分析代码。
首先是数据的初始化及输入。如下:
int N;
scanf("%d",&N);
int i;
for(i=1;i<=N;i++)
father[i]=i;
for(i=1;i<=N;i++)//i号人
{
int M;
scanf("%d : ",&M);
int j;
for(j=0;j<M;j++)//第j个活动
{
int k;
scanf("%d",&k);
if(dalao[k]==0)//k号活动没有大佬
dalao[k]=i;
else
Union(i,dalao[k]);
}
}
在这期间可以完成dalao数组的初始化和合并工作。
注意数据输入时,冒号要在scanf的格式化输入中,否则会出错。
合并函数如下:
void Union(int a,int b)
{
int A=findFather(a);
int B=findFather(b);
if(A!=B)
father[A]=B;
}
寻找根节点的函数也很简单,如下:
int findFather(int a)
{
int b=a;//用b保存a,a是叶子,去找a的根
while(a!=father[a])
a=father[a];//循环退出后,a就是a的根
while(b!=father[b])//路径压缩就是,先找一遍,找到根,再找一遍,路上把所有的father改成根
{
int c=father[b];
father[b]=a;
b=c;
}
return a;
}
路径压缩简而言之就是,第一次沿路径追溯,找到根节点,第二次沿路径追溯,在追溯过程中将途中的节点的father都改为根节点。
接下来遍历father数组,新开一个num数组,数组下标是根节点的下标,值为以此下标为根节点的节点数量,然后遍历father,寻找每一个节点的根节点并累加,如下:
for(i=1;i<=N;i++)
{
int i_father=findFather(i);
num[i_father]++;
}
易知,num中非零值的个数就是总的社交圈的数量,由于要以递减形式输出所有社交圈的数量,用sort对num进行排序即可,不用怕num的值与下标的对应关系被打乱,这个关系又不用输出,再说关系也是有多个的,没有标准答案。
三、总结
并查集的稍复杂应用,要记牢findFather和Union的写法。
PS:代码如下:
#include<stdio.h>
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
/*
需要两个数组,father用于存储每个人的上级是谁,course用于存储某一项活动的大佬
开始时每个人的上级都是自己
然后输入每个人喜欢的活动,如果该活动还没有大佬,那么这个人就成为该活动的大佬
如果已经有大佬,那么就将这个人与大佬合并
例如三号五号和七号
三号喜欢5、3五号喜欢3七号喜欢6、8、1、5
那么简单起见,3、5的大佬都是三号
对于五号来说,他不能做3的大佬,就只能和3的大佬,也就是三号合并
对于七号来说,他可以做6、8、1的大佬,不能做5的大佬,只能和5的大佬,也就是三号合并
这样三号、五号、七号就是一个社交集合了
*/
int father[1010]={0};
int dalao[1010]={0};
int num[1010]={0};
int findFather(int a)
{
int b=a;//用b保存a,a是叶子,去找a的根
while(a!=father[a])
a=father[a];//循环退出后,a就是a的根
while(b!=father[b])//路径压缩就是,先找一遍,找到根,再找一遍,路上把所有的father改成根
{
int c=father[b];
father[b]=a;
b=c;
}
return a;
}
void Union(int a,int b)
{
int A=findFather(a);
int B=findFather(b);
if(A!=B)
father[A]=B;
}
bool cmp(int a,int b)
{
return a>b;
}
int main()
{
int N;
scanf("%d",&N);
int i;
for(i=1;i<=N;i++)
father[i]=i;
for(i=1;i<=N;i++)//i号人
{
int M;
scanf("%d : ",&M);
int j;
for(j=0;j<M;j++)//第j个活动
{
int k;
scanf("%d",&k);
if(dalao[k]==0)//k号活动没有大佬
dalao[k]=i;
else
Union(i,dalao[k]);
}
}
for(i=1;i<=N;i++)
{
int i_father=findFather(i);
num[i_father]++;
}
int sum=0;
for(i=1;i<=1000;i++)
if(num[i]!=0)
sum++;
printf("%d\n",sum);
sort(num,num+1010,cmp);
for(i=0;i<sum-1;i++)
printf("%d ",num[i]);
printf("%d",num[i]);
}