PTA L2-007 家庭房产 (25 分) (详解)
给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。
输入格式:
输入第一行给出一个正整数N(≤1000),随后N行,每行按下列格式给出一个人的房产:
编号 父 母 k 孩子1 ... 孩子k 房产套数 总面积
其中编号
是每个人独有的一个4位数的编号;父
和母
分别是该编号对应的这个人的父母的编号(如果已经过世,则显示-1
);k
(0≤k
≤5)是该人的子女的个数;孩子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; }