PAT甲级备考——并查集

PAT甲级备考——并查集

  • 题目
    • 【1107】并查集
    • 【114】并查集

题目

PAT (Advanced Level) Practice

【1107】并查集
【1114】 并查集
【1118】并查集()

并查集知识点参考:https://zhuanlan.zhihu.com/p/29035169

【1107】并查集

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;
}

【114】并查集

题⽬⼤意:给定每个⼈的家庭成员和其⾃⼰名下的房产,请你统计出每个家庭的⼈⼝数、⼈均房产⾯积及房产套数。⾸先在第⼀⾏输出家庭个数(所有有亲属关系的⼈都属于同⼀个家庭)。随后按下列
格式输出每个家庭的信息:家庭成员的最⼩编号 家庭⼈⼝数 ⼈均房产套数 ⼈均房产⾯积。其中⼈均值要求保留⼩数点后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;
}

你可能感兴趣的:(PAT,图论,数据结构,算法)