PAT (Advanced Level) Practice 1114 Family Property 并查集应用

一、概述

题设太麻烦了,就是用并查集。

唯一的误区就在于,我错误理解了并查集的意思。我本以为,并查集执行完所有合并操作后,属于同一个集合的元素,他们的father数组对应的值都相等,实际上,是这些元素的fingFather的返回值都相等。这个错误让我debug了好久。难过。

二、分析

首先是观察数据。数据有

本人、父母、孩子、房产数量、房产面积。

考虑使用两个数组,一个是father数组,负责维护各节点关系,一个是节点数组,负责储存节点信息。

初始化father数组。每个人的father都是他自己。

for(int i=0;i<10010;i++)
	{
		father[i]=i;
	}

并查集,简而言之就是根据某些联系把具有联系的节点放进一个集合,这里的联系,指的就是父母联系和孩子联系。于是,在每个节点输入时,输入父亲,要与本人Union一次,输入母亲,孩子,也都要Union一次。而节点数组,则只需要储存房产数量和房产面积即可。

father数组和节点数组均采用固定大小。如下输入:

int N;
	scanf("%d",&N);
	set<int> member;
	for(int i=0;i<N;i++)
	{
		int people,dad,mom;
		scanf("%d %d %d",&people,&dad,&mom);
		member.insert(people);
		if(dad!=-1)
			{
				member.insert(dad);
				Union(people,dad);
			}
		if(mom!=-1)
			{
				member.insert(mom);
				Union(people,mom);
			}
		int childnum;
		scanf("%d",&childnum);
		for(int j=0;j<childnum;j++)
		{
			int childname;
			scanf("%d",&childname);
			member.insert(childname);
			Union(childname,people);
		}
		if(dad!=-1)
			{
				member.insert(dad);
				Union(people,dad);
			}
		if(mom!=-1)
			{
				member.insert(mom);
				Union(people,mom);
			}
		//Union(people,dad);//*************
		//Union(people,mom);//**********
		double set,area;
		scanf("%lf %lf",&set,&area);
		node[people].set=set;
		node[people].area=area;
	}

注意到一点,由于我的节点数组都是采用固定大小,因此还需要一个数组储存本次样例中的所有人,不然没法在一万个节点里面把我需要的拿出来。选择在输入本人、父母、孩子时将其存入一个set。不能选择vector,因为一个人可能既是父母又是孩子,这样可能存储多次导致重复。这要注意。把所有涉及人员存入member set。

Union函数如下:

void Union(int a,int b)
{
	int f1=Findfather(a);
	int f2=Findfather(b);
	if(f1<f2)
		father[f2]=f1;
	else if(f1>f2)
		father[f1]=f2;
}

平时的Union函数只需要判断f1!=f2即可,但是本题要求父亲是最小的那个,因此选择令较小的当父亲。

Findfather函数如下,使用了压缩路径:

int Findfather(int root)
{
	int real=root;
	while(root!=father[root])
		root=father[root];
	while(real!=father[real])
	{
		int temp;
		temp=real;
		father[temp]=root;
		real=father[real];
	}
	return real;
}

这里就是我最开始一直蛋疼的地方了。我发现无论如何,都没法保证同一个集合的所有元素,他们的father数组都是一个值。举例如下:

a的父母为b、c,孩子为e、f,那么假设abce由小到大为cbaef,在Union之后,a、b、c、e、f的father值分别为:

a与b合并,F(a)=b,F(b)=b;

a与c合并,F(a)=b,F(b)=c,F(c)=c;

a与e合并,F(a)=b,F(b)=c,F(e)=c;

a与f合并,F(a)=b,F(b)=c,F(f)=c。

可以看出来,执行五次Union操作之后,它们形成一棵树,根为c,但是并不是树高为2,而是3,因为a的father一直是b,而b的father是c。这说明的确无法更改直接父亲b,但是可以找到最终父亲,都是c。

这一点明确之后,下面的就好做了。

找到father数组中所有的a=F(a)的,他们就是各自的一家之主。共有多少家庭也就找到了。

vector<int> host;
	set<int>::iterator it;
	for(it=member.begin();it!=member.end();it++)
	{
		//printf("%04d父亲是%04d\n",*it,father[*it]);
		if(*it==father[*it])
		{
			host.push_back(*it);
		}
	}

然后遍历member set,开一个家庭结构体数组,用于储存每个家庭的财产信息。使用Findfather找出是一个家庭的成员并相加。

如下:

for(it1=host.begin();it1!=host.end();it1++)
	{
		int numsum=0;
		double setsum=0;
		double areasum=0;
		set<int>::iterator it2;
		for(it2=member.begin();it2!=member.end();it2++)
		{
			if(Findfather(*it2)==*it1)//注意是Findfather(*it2)而不是father(*it2) 
			{
				numsum++;
				setsum+=node[*it2].set;
				areasum+=node[*it2].area;
			}
		}
		family[familynum].father=*it1;
		family[familynum].num=numsum;
		family[familynum].area=areasum/numsum;
		family[familynum].set=setsum/numsum;
		familynum++;
	}

然后对此结构体数组排序并输出即可。

三、总结

很经典的并查集问题,考察并查集性质。

PS:代码如下:

#include<stdio.h>
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<vector>
#include<set>
#include<algorithm>
using namespace std;
int father[10010];
struct Node
{
	double set=0;
	double area=0;
}node[10010];
struct Family
{
	int father;
	int num=0;
	double set=0;
	double area=0;
}family[10010];
int Findfather(int root)
{
	int real=root;
	while(root!=father[root])
		root=father[root];
	while(real!=father[real])
	{
		int temp;
		temp=real;
		father[temp]=root;
		real=father[real];
	}
	return real;
}
void Union(int a,int b)
{
	int f1=Findfather(a);
	int f2=Findfather(b);
	if(f1<f2)
		father[f2]=f1;
	else if(f1>f2)
		father[f1]=f2;
}
bool cmp(Family a,Family b)
{
	if(a.area!=b.area)
		return a.area>b.area;
	else
		return a.father<b.father;
}
int main()
{
	for(int i=0;i<10010;i++)
	{
		father[i]=i;
	}
	int N;
	scanf("%d",&N);
	set<int> member;
	for(int i=0;i<N;i++)
	{
		int people,dad,mom;
		scanf("%d %d %d",&people,&dad,&mom);
		member.insert(people);
		if(dad!=-1)
			{
				member.insert(dad);
				Union(people,dad);
			}
		if(mom!=-1)
			{
				member.insert(mom);
				Union(people,mom);
			}
		int childnum;
		scanf("%d",&childnum);
		for(int j=0;j<childnum;j++)
		{
			int childname;
			scanf("%d",&childname);
			member.insert(childname);
			Union(childname,people);
		}
		if(dad!=-1)
			{
				member.insert(dad);
				Union(people,dad);
			}
		if(mom!=-1)
			{
				member.insert(mom);
				Union(people,mom);
			}
		//Union(people,dad);//*************
		//Union(people,mom);//**********
		double set,area;
		scanf("%lf %lf",&set,&area);
		node[people].set=set;
		node[people].area=area;
	}
	
	vector<int> host;
	set<int>::iterator it;
	for(it=member.begin();it!=member.end();it++)
	{
		//printf("%04d父亲是%04d\n",*it,father[*it]);
		if(*it==father[*it])
		{
			host.push_back(*it);
		}
	}
	int familynum=host.size();
	printf("%d\n",familynum);
	familynum=0;
	vector<int>::iterator it1;
	int setnum=member.size();
	for(it1=host.begin();it1!=host.end();it1++)
	{
		int numsum=0;
		double setsum=0;
		double areasum=0;
		set<int>::iterator it2;
		for(it2=member.begin();it2!=member.end();it2++)
		{
			if(Findfather(*it2)==*it1)//注意是Findfather(*it2)而不是father(*it2) 
			{
				numsum++;
				setsum+=node[*it2].set;
				areasum+=node[*it2].area;
			}
		}
		family[familynum].father=*it1;
		family[familynum].num=numsum;
		family[familynum].area=areasum/numsum;
		family[familynum].set=setsum/numsum;
		familynum++;
	}
	sort(family,family+familynum,cmp);
	for(int i=0;i<familynum;i++)
	{
		printf("%04d %d %.3lf %.3lf\n",family[i].father,family[i].num,family[i].set,family[i].area);
	}
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值