PAT (Advanced Level) Practice
【1107】并查集
【1114】 并查集
【1118】并查集()
并查集知识点参考:https://zhuanlan.zhihu.com/p/29035169
1107 Social Clusters (30 分)
题⽬⼤意:有n个⼈,每个⼈喜欢k个活动,如果两个⼈有任意⼀个活动相同,就称为他们处于同⼀个社交⽹络。求这n个⼈⼀共形成了多少个社交⽹络。
#include
#include
#include
using namespace std;
int N;
vector<int> fa, node_rank;
vector<int> hobbies[1005];
// 初始化,每个节点的父节点是自身,每个节点目前的秩为1,即以该节点为父节点的子树深度为1
inline void init(){
for(int i=1; i<=N; i++){
fa[i] = i;
node_rank[i] = 1;
}
}
// 根据下标i,找到它的父亲根节点;迭代赋值
int find(int i){
if(fa[i] == i) return i;
else{
fa[i] = find(fa[i]);
return fa[i];
}
}
// 合并两个子树a和b,先分别找到a和b的最深的根节点,将rank较小的树合并到rank较大的树下面,为了减少查找难度;
// 如果两个树一样大,则合并,并将父子树的rank值加1
void merge(int a, int b){
int x = find(a), y = find(b);
if(node_rank[x] >= node_rank[y]){
fa[y] = x;
}else if(node_rank[x] < node_rank[y]){
fa[x] = y;
}
if(node_rank[x] == node_rank[y])
node_rank[x]++;
}
bool cmp(int a, int b){
return a>b;
}
int main(){
// 1. 初始化
cin>>N;
fa.resize(N+1);
node_rank.resize(N+1);
init();
// 2.读入每个人hobbies,根据hobby来合并
int k, h;
for(int i=1; i<=N; i++){
scanf("%d:",&k); // 每个人有k个hobby
while(k--){
scanf("%d",&h);
if(hobbies[h].size()!=0){ // 如果已经有人喜欢第h个hobby了,则将新人i合并到这个hobby的第一个人的子树下
merge(hobbies[h][0], i);
}
hobbies[h].push_back(i); // 第h个hobby中加入新人i
}
}
// 3. 统计多少组人
int group_num = 0;
vector<int> groups(N+1);
for(int i=1; i<=N; i++)
groups[find(i)]++; // 找到每个人的根节点,相同的根节点意味着是同一组人,则该节点对应的人数++
for(int i=1; i<=N; i++)
if(groups[i]!=0) group_num++; // 如果某个根节点下有人则组数++
sort(groups.begin(), groups.end(), cmp); // groups内部按照人数倒叙排列
// 4. 输出
cout<<group_num<<endl;
for(int i=0; i<group_num; i++){
printf("%d%s", groups[i], i==group_num-1?"\n":" ");
}
return 0;
}
题⽬⼤意:给定每个⼈的家庭成员和其⾃⼰名下的房产,请你统计出每个家庭的⼈⼝数、⼈均房产⾯积及房产套数。⾸先在第⼀⾏输出家庭个数(所有有亲属关系的⼈都属于同⼀个家庭)。随后按下列
格式输出每个家庭的信息:家庭成员的最⼩编号 家庭⼈⼝数 ⼈均房产套数 ⼈均房产⾯积。其中⼈均值要求保留⼩数点后3
两个数据结构:首先将所有的输入数据保存至data中,数据结构为自定义的person;进而转化成ans输出数组,数据结构为answer;
并查集的常规merge操作在输入阶段进行,且merge操作保障根节点一定是id值最小的人
维护visited数组,所有出现过的人均置为true;
ans数组中,is_root为true的人就是根节点,计算根节点对应的各种平均值
注意:
这里所有的亲属关系不区分爸爸妈妈(男女),亲属关系中的父子,在并查集中并不一定是父子,merge规则设定与”输出最小id的人“相适应;
printf(“%04d”, data) 将自动补齐0
#include
#include
#include
#include
#include
using namespace std;
// 输入数据的数据结构person
struct person{
int id, fid, mid, m_estate, area;
int child[10];
}data[1005];
// 输出答案的数据结构answer
struct answer{
/*
id: root节点对应的id
number: 这个节点所属的group有多少人
m_estate: 资产的数目
area: 资产的平均面积
is_root: 是否是根节点
*/
int id, number;
double m_estate, area;
bool is_root = false;
}ans[10000];
int N;
int fa[10000]; // 保存每个人的前驱节点的index
bool visited[10000]; // 由于输入10个人的信息,但是涉及的大于10人,因此维持visited数组,保存有谁出现过
void init(){
/*
初始化所有人的前驱节点为自身
*/
for(int i=0; i<10000; i++){
fa[i] = i;
}
}
int find(int index){
/*
找到index节点对应的根节点
*/
while(index != fa[index])
index = fa[index];
return index;
}
void merge(int a, int b){
/*
合并,保证:节点id值越小越靠近根
*/
int root_a = find(a);
int root_b = find(b);
if(root_a > root_b)
fa[root_a] = root_b;
else if(root_a < root_b)
fa[root_b] = root_a;
}
bool cmp(answer x, answer y){
if(x.area != y.area){
return x.area > y.area;
}else{
return x.id < y.id;
}
}
int main(){
/* (1)
数据输入data;
对每个出现的id设置visited[id]=true
merge当前节点和他的父节点
*/
cin>>N;
init();
int k, m_estate, area;
for(int i=0; i<N; i++){
scanf("%d %d %d %d", &data[i].id, &data[i].fid, &data[i].mid, &k);
visited[data[i].id] = true;
if(data[i].fid != -1){
merge(data[i].fid,data[i].id);
visited[data[i].fid] = true;
}
if(data[i].mid != -1){
merge(data[i].mid,data[i].id);
visited[data[i].mid] = true;
}
for(int ik=0; ik<k; ik++){
scanf("%d", &data[i].child[ik]);
merge(data[i].child[ik], data[i].id);
visited[data[i].child[ik]] = true;
}
scanf("%d %d", &data[i].m_estate, &data[i].area);
}
/* (2)
对data中的数据,找到每个id对应的根节点r
统计每个group,将r对应的数据更新至ans数组中的id、m_estate、area、is_root
*/
for(int i=0; i<N; i++){
int r = find(data[i].id);
ans[r].id = r;
ans[r].m_estate += data[i].m_estate;
ans[r].area += data[i].area;
ans[r].is_root = true;
}
/* (3)
根据是否被访问过,更新ans数组中的number
并计算总的group数量
*/
int group_number=0;
for(int i=0; i<10000; i++){
if(visited[i]){
ans[find(i)].number+=1; // 如果i被访问过,则i的根节点find(i)对应的答案数组中,数目++
}
if(ans[i].is_root){
group_number++; // 如果ans中节点对应的is_root为true,则是根节点,总的组数++
}
}
cout<<group_number<<endl;
/* (5)
对每个group,将m_estate和area都更新成平均值
取出所有的根节点,存入temp中
对temp按照一定的规则排序
并将temp中的数据输出
*/
vector<answer> temp;
for(int i=0; i<10000; i++){
if(ans[i].is_root){
ans[i].m_estate = (double)(ans[i].m_estate*1.0/ans[i].number);
ans[i].area = (double)(ans[i].area*1.0/ans[i].number);
temp.push_back(ans[i]);
}
}
sort(temp.begin(), temp.end(), cmp);
for(int i=0; i<group_number; i++){
printf("%04d %d %.3f %.3f\n", temp[i].id, temp[i].number, temp[i].m_estate, temp[i].area);
}
return 0;
}