PAT (Advanced Level) Practice 1107 Social Clusters 并查集应用

一、概述

根据爱好的活动分类,这题出的有缺陷,我只看题目根本看不出来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]);	
} 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值