并查集
并查集是一种维护集合的数据结构,它的名字中“并”“查”“集”分别取自Union(合并),Find(查找),Set(集合)这3个单词。
初始化
一开始每个元素都是独立的一个集合
for(int i=1;i<=N;i++)
{
fa[i]=i;
}
查找
由于规定同一个集合中只存在一个根结点,因此查找操作就是对给定的结点寻找其根节点的过程
递推
int find(int x)
{
while(x!=fa[x])
{
x=fa[x];
}
return x;
}
递归
int find(int x)
{
if(x==fa[x])
{
return x;
}
else{
return find(fa[x]);
}
}
路径压缩
int find(int x)
{
if(x==fa[x])
{
return x;
}
else{
fa[x]=find(fa[x])
return fa[x];
}
}
合并
void unio(int i,int j)
{
int x1=find(i);
int x2=find(j);
fa[x1]=x2;
}
L2-024 部落 (25 分)
题意
在一个社区里,每个人都有自己的小圈子,还可能同时属于很多不同的朋友圈。我们认为朋友的朋友都算在一个部落里,于是要请你统计一下,在一个给定社区中,到底有多少个互不相交的部落?并且检查任意两个人是否属于同一个部落。
思路
- 初始化,将属于同一部落的进行合并
- 用set计算共有多少人
- 有多少个互不相交的部落,遍历所有人如果那个人的祖先就是自己,多一个部落
- 任意两个人如果祖先相同就是同一部落
坑点
- 要先将第一个人存入部落方便后面的合并
算法一:set+并查集
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int fa[N];
int find(int i)//查找
{
if(i==fa[i])
{
return i;
}
else{
fa[i]=find(fa[i]);
return fa[i];
}
}
void unio(int i,int j)//合并
{
int x1=find(i);
int x2=find(j);
fa[x2]=x1;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<=N;i++)//初始化
{
fa[i]=i;
}
set<int>st;//set计算共有多少人
for(int i=0;i<n;i++)
{
int k,x,x1;
cin>>k;
if(k>0)
{
cin>>x;//先将第一个人存入部落方便后面的合并
st.insert(x);
}
for(int j=1;j<k;j++)
{
cin>>x1;
st.insert(x1);
unio(x,x1);//依次合并为同一祖先
}
}
int ans=0;//计算共有多少不同部落
for(int i=1;i<=st.size();i++)
{
if(fa[i]==i)//如果那个人的祖先就是自己,多一个部落
{
ans++;
}
}
cout<<st.size()<<" "<<ans<<endl;
int f;
cin>>f;
while(f--)
{
int a,b;
cin>>a>>b;
if(find(a)==find(b))//如果两个人的祖先相同
{
cout<<"Y"<<endl;
}
else{
cout<<"N"<<endl;
}
}
return 0;
}
总结
并查集模板题
L2-007 家庭房产 (25 分)
题意
给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。
思路
- 完成并查集的初始化,查找,合并
- 利用3个结构体分别存放初始输入家庭信息,统计家庭房产,面积信息,合并后最后输出家庭信息
坑点
- 注意排序的规律
- 判断那个人是否还活着(是否不为-1)
- 最后的输出不重复
算法一:并查集+结构体
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct name {
int id;//编号
int sum;//家庭人口数
double fct;//房产套数
double area;//面积
}a[N];
int fa[N];//祖先
int st[N]={0};//判断编号是否存在
bool cmp(name a,name b)//家庭信息首先按人均面积降序输出,若有并列,则按成员编号的升序输出。
{
if(a.area==b.area)
{
return a.id<b.id;
}
else{
return a.area>b.area;
}
}
int find(int i)//查找
{
if(i==fa[i])
{
return i;
}
else{
fa[i]=find(fa[i]);
return fa[i];
}
}
void unio(int i,int j)//合并
{
int x1=find(i);
int x2=find(j);
if(x1>x2)//需要编号最小的输出
{
fa[x1]=x2;
}
else
{
fa[x2]=x1;
}
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=N;i++)//初始化
{
fa[i]=i;
}
for(int i=0;i<n;i++)
{
int fat,mom;//父亲编号,母亲编号
cin>>a[i].id>>fat>>mom;
st[a[i].id]=1;
if(fat!=-1)
{
unio(a[i].id,fat);//合并祖先
st[fat]=1;
}
if(mom!=-1)
{
unio(a[i].id,mom);//合并祖先
st[mom]=1;
}
int k;
cin>>k;
while(k--)
{
int idx;
cin>>idx;
if(idx!=-1)//当人没死才算在家庭成员里
{
unio(idx,a[i].id);
st[idx]=1;
}
}
cin>>a[i].fct>>a[i].area;
}
name jt[N];//记录家庭信息
for(int i=0;i<n;i++)//统计家庭房产,面积
{
int id=find(a[i].id);
jt[id].id=id;
jt[id].area+=a[i].area;
jt[id].fct+=a[i].fct;
}
for(int i=0;i<N;i++)//统计家庭成员
{
if(st[i]==1)//只有当人存在时
{
jt[find(i)].sum++;
}
}
int cnt=0;//记录总家庭数
int st2[N]={0};//不重复计算同一家庭
name ans[N];//记录合并后的家庭情况
for(int i=0;i<N;i++)
{
if(st[i]==1)
{
int id=find(i);
if(st2[id]==0)
{
st2[id]=1;
ans[cnt].fct=jt[id].fct/(1.0*jt[id].sum);
ans[cnt].area=jt[id].area/(1.0*jt[id].sum);
ans[cnt].id=id;
ans[cnt++].sum=jt[id].sum;
}
}
}
cout<<cnt<<endl;
sort(ans,ans+cnt,cmp);//按要求排序输出
for(int i=0;i<cnt;i++)
{
printf("%04d %d %.3lf %.3lf\n",ans[i].id,ans[i].sum,ans[i].fct,ans[i].area);
}
return 0;
}
总结
灵活运用结构体,并查集