PTA L2-007 家庭房产 (25 分) (详解)

给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。

输入格式:

输入第一行给出一个正整数N(1000),随后N行,每行按下列格式给出一个人的房产:

编号 父 母 k 孩子1 ... 孩子k 房产套数 总面积
 

其中编号是每个人独有的一个4位数的编号;分别是该编号对应的这个人的父母的编号(如果已经过世,则显示-1);k0k5)是该人的子女的个数;孩子i是其子女的编号。

输出格式:

首先在第一行输出家庭个数(所有有亲属关系的人都属于同一个家庭)。随后按下列格式输出每个家庭的信息:

家庭成员的最小编号 家庭人口数 人均房产套数 人均房产面积
 

其中人均值要求保留小数点后3位。家庭信息首先按人均面积降序输出,若有并列,则按成员编号的升序输出。

 

 

输入样例:

10
6666 5551 5552 1 7777 1 100
1234 5678 9012 1 0002 2 300
8888 -1 -1 0 1 1000
2468 0001 0004 1 2222 1 500
7777 6666 -1 0 2 300
3721 -1 -1 1 2333 2 150
9012 -1 -1 3 1236 1235 1234 1 100
1235 5678 9012 0 1 50
2222 1236 2468 2 6661 6662 1 300
2333 -1 3721 3 6661 6662 6663 1 100

输出样例:

3
8888 1 1.000 1000.000
0001 15 0.600 100.000
5551 4 0.750 100.000

----------------------

这个题目可以用两种方法实现

第一种:把它当成图来处理,广搜或深搜一遍就可以了,对图的遍历比较熟悉的广搜和深搜应该不是什么难点,那主要是看建图了,理论上以何种方式建图都可以达到目的选择最方便的那个,对于节点序号不是像0 1 2 3....那简单的图就需要一点技巧了,建一个很大的数组用来存编号,直接用map映射到0 1 2 3....(这样就得考虑后续是否还需要通过下标0 1 2 3...找到对应编号,因为map并不是双向映射,从左到右好找,从右往左就不好找了,或许可以用两个map来实现双向映射),不过我用的解法是建一个很大的数组(这应该是通常用的方法吧)。。。。不过用建图的方式写的答案只能得22分最后有一个测试点没过,我也没去深究其错在哪了。。如果哪位好奇宝宝发现我哪里还少考虑了可以留言告诉我,非常感谢!

代码如下

//链式前向星,无向图,双向建边
//或许很多数组可以用vector来节省空间,不过无所谓了
#include <bits/stdc++.h>
using namespace std;
vector<int> head(10000, -1); //所有人,下标对应于编号
set<int> All;                //记录所有人的编号,用于处理单个点的情况
struct Room
{
    int num;   //房产数量
    int S;     //总面积
} room[10000]; //对应每个编号人的房产情况

struct Edge
{
    int to;
    int next;
} edge[10050];
int ep = 0; //边数

struct answer
{
    int midid = 9999; //最小编号
    int num;          //人数
    double rooms;     //房产数
    double S;         //面积
} res[1005];          //存储结果的数组
int rp = 0;           //家庭个数
int vis[10050];       //已访问

void dfs(int to)
{
    if (All.count(to))
        All.erase(to);
    //其实这一步是刚开始没考虑好的缘故,发现答案不对才加的这一步(滑稽)
    //深搜中所有出现过的点至少有两个点连在一起的,因为判断终止条件就是指针是否指到-1,
    //而head默认值就是-1,如果某个节点它没有连接到其他点那么它head值就是-1,
    //此时判断到这里的时候就会直接跳过,所有用set来保存所有节点访问到的就删除,留下的就全是未与其他点相连的点了

    int p = head[to]; //指针,用来操作邻边
    //res都是用来记录结果的
    res[rp].num++;
    res[rp].midid = min(to, res[rp].midid);
    res[rp].rooms += room[to].num;
    res[rp].S += room[to].S;
    while (p != -1) //循环每个连通部分,访问之后vis数组置1
    {
        int to = edge[p].to;
        if (vis[to] == 0)
        {
            vis[to] = 1;
            dfs(to);
        }
        p = edge[p].next;
    }
}
//排序比较函数
bool cmp(answer &a1, answer &a2)
{
    if (a1.S / a1.num == a2.S / a2.num)
        return a1.midid < a2.midid;
    return a1.S / a1.num > a2.S / a2.num;
}

void test()
{
    int n; //总人数
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int t1, t2, t3, t4; //分别表示编号 本人 父 母 孩子
        int kidnum;         //孩子个数
        cin >> t1 >> t2 >> t3 >> kidnum;
        All.insert(t1); //存储主体
        //父母建边
        if (t2 != -1)
        {
            edge[ep].to = t2;
            edge[ep].next = head[t1];
            head[t1] = ep++;
            //双向
            edge[ep].to = t1;
            edge[ep].next = head[t2];
            head[t2] = ep++;
        }
        if (t3 != -1)
        {
            edge[ep].to = t3;
            edge[ep].next = head[t1];
            head[t1] = ep++;
            //双向
            edge[ep].to = t1;
            edge[ep].next = head[t3];
            head[t3] = ep++;
        }
        //孩子建边
        for (int j = 1; j <= kidnum; j++)
        {
            cin >> t4;
            edge[ep].to = t4;
            edge[ep].next = head[t1];
            head[t1] = ep++;
            //双向
            edge[ep].to = t1;
            edge[ep].next = head[t4];
            head[t4] = ep++;
        }
        cin >> room[t1].num >> room[t1].S;
    }
    //循环遍历
    for (int i = 0; i < 10000; i++)
    {
        if (head[i] != -1 && vis[i] == 0)
        {
            vis[i] = 1;
            dfs(i);
            rp++;
        }
    }
    //单点情况处理
    if (!All.empty()) //如果set中不为空说明有点未与其他点相连
    {
        for (int i : All)
        {
            res[rp].num++;
            res[rp].S = room[i].S;
            res[rp].midid = i;
            res[rp].rooms = room[i].num;
            rp++;
        }
    }
    //排序
    sort(res, res + rp, cmp);

    //答案输出
    cout << rp << endl;
    for (int i = 0; i < rp; i++)
    {
        printf("%04d %d %.3lf %.3lf\n", res[i].midid, res[i].num, res[i].rooms / res[i].num, res[i].S / res[i].num);
    }
}

int main()
{
    test();
    return 0;
}

 

第二种:并查集,用并查集的好处是题目中要求找出最小的下标,并查集需要用一个特征来表示唯一的祖先(最大、最小.....,实际没有这个特征其实也可以用,似乎说了跟没说一样哈哈)

这种解法就比上一种解法方便许多,主要需要  fa数组表示各编号的祖先,一个set集合用来记录所有出现过的人的编号,然后就是一个够大的数组记录房产信息(这里其实可以完全不用数组,用一个map编号直接映射房产信息的结构体就完了,这更节省空间),用并查集写很方便的地方在于不用创建很大的数组(除了fa数组),还有就是只需要遍历所有编号就能得出所有结果了,遍历的顺序对结果没有任何影响,因为只要是一个家族里面的,其祖先就是唯一的(最小的那个下标)

代码如下

//并查集
#include <bits/stdc++.h>
using namespace std;
int fa[10050]; //记录祖先
set<int> peo;  //记录所有人的编号,在输入的时候记录就行了,有的编号输入时出现不止一次所有用set确保唯一
struct people  //房产信息,完全可以用map来代替head数组
{
    int rooms;
    double S;
} head[10050];
struct answer //结果,一个家族的全部信息,用最小编号映射家族信息
{
    int minid;
    int num;
    int rooms;
    double S;
}; //家庭个数
map<int, answer> mp;
void init() //fa数组初始化
{
    for (int i = 0; i < 10050; i++)
        fa[i] = i;
}

int Find(int a) //找祖先
{
    if (fa[a] == a)
        return a;
    else
        return fa[a] = Find(fa[a]);
}

void Union(int a, int b) //合并祖先
{
    int f1 = Find(a);
    int f2 = Find(b);
    if (f1 < f2)     //最小的为祖先
        fa[f2] = f1; //修改的是祖先的祖先,祖先的祖先就是其本身
    else
        fa[f1] = f2;
}
//排序比较函数
bool cmp(answer &a1, answer &a2)
{
    if (a1.S / a1.num == a2.S / a2.num)
        return a1.minid < a2.minid;
    return a1.S / a1.num > a2.S / a2.num;
}

void test()
{
    init();
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int id, p1, p2, knum, k;//分别代表 本人 父 母 孩子数量 孩子
        cin >> id >> p1 >> p2 >> knum;
        peo.insert(id);//记录所有编号
        if (p1 != -1)
        {
            peo.insert(p1);//记录所有编号
            Union(id, p1);
        }
        if (p2 != -1)
        {
            peo.insert(p2);//记录所有编号
            Union(id, p2);
        }
        for (int j = 0; j < knum; j++)
        {
            cin >> k;
            peo.insert(k);//记录所有编号
            Union(id, k);
        }
        cin >> head[id].rooms >> head[id].S;
    }

    for (int i : peo)//遍历set,只需要得到编号即可,mp中保存的是最后结果
    {
        int id = Find(i);
        mp[id].minid = id;
        mp[id].num++;
        mp[id].S += head[i].S;
        mp[id].rooms += head[i].rooms;
    }
    cout << mp.size() << endl;
    vector<answer> res;//转到数组中对其进行排序输出
    for (auto i : mp)
    {
        res.push_back(i.second);
    }
    //排序
    sort(res.begin(), res.end(), cmp);
    //输出结果
    for (auto i : res)
        printf("%04d %d %.3lf %.3lf\n", i.minid, i.num, 1.0 * i.rooms / i.num, 1.0 * i.S / i.num);
}

int main()
{
    test();
    return 0;
}

 

posted @ 2022-03-28 20:46  深情的山鸡  阅读(1167)  评论(1编辑  收藏  举报