PAT甲级114
点击上面是题目
题意给出n个家庭信息,家庭的家长id 父亲的id 母亲的id k个孩子的id 一共有几套房 房子的总面积
要求求出一个家族中编号id最小的人,家族中的总人数, 家族的每个人平均房产面积,以及平均个人拥有的房屋套数。
答案按照平均面积的非升序输出,如果有平局,按照最小id成员的非降序输出。
我是用连同块做的,我直接把父母也当作孩子了,然后构建了一个无向图,这样孩子可以到达父母,父母也可以到达孩子,只要能够达到就说明是一个家族的,这样用dfs遍历一个块统计结点数就可以了,然后再统计的同时计算房屋总面积,总套数,以及成员数。
我觉得我的解法好像有点不是很正规,因为我把父母当作孩子来处理了,我总觉得有点怪怪的。。。。。。
代码注释比较详细:
补充一下,我刚刚觉得自己的解法不是正解,看了一下其他人的解法,发现我的还是不错的,由于是连同块问题所以可以用dfs也可以用并查集,并查集我开始想到了,然后在谁当父亲结点以及怎么记录一个块的最小结点的问题上,还有如果把资源合并的问题上没想明白,只想到了dfs来做,可能是思维模式没有这么想过吧
后面会补充到并查集的做法,代码贴在我dfs解法下面
#include<bits/stdc++.h>
using namespace std;
const int N = 10001;
int n;
struct node { // 图
vector<int> child_id;
int sets;
int area;
node() {
sets = area = 0;
}
}tree[N]; //就是一个图
struct famlly { //用来记录每一个家族的信息
int min_id;
int members;
double area;
double sets;
double avg_areas;
}temp;
vector<famlly> path; // 家族信息的集合
bool vis[N];
int vislist[N];
bool cmp(const famlly &a, const famlly &b) {
if(a.avg_areas != b.avg_areas) return a.avg_areas > b.avg_areas;
else return a.min_id < b.min_id;
}
void DFS(int u) {
if(vis[u] == false) {
temp.members++; //这是一个新的家庭成员 记录一波
tree[u].area != 0 ? temp.area += tree[u].area : 2333333;
tree[u].sets != 0 ? temp.sets += tree[u].sets : 2333333;
}
vis[u] = true; //防止重复计算
u < temp.min_id ? temp.min_id = u : 233; //刷新一下最小编号的家庭成员
for(int i = 0; i < tree[u].child_id.size(); i++) { //看看这个成员是否可以做出贡献 探出其他的家庭成员
int v = tree[u].child_id[i]; //拿到可能没访问的家庭成员
if(vis[v] == false) { //确定该成员是否有必要访问
DFS(v); //访问他
}
}
}
void DFSTrave() {
memset(vis, false, sizeof(vis)); //这个数组只用初始化一次
for(int i = 0; i < n; i++) {
int u = vislist[i]; //只用从有房的的结点开始遍历就可以了,就是看看与其他有房的块连同不连同,
if(vis[u] == false) { //因为有房的家庭题目都给出了,所有不存在从有房的结点遍历会漏人的情况
temp.min_id = 0x3fffffff; //状态初始化
temp.members = 0;
temp.area = 0;
temp.sets = 0;
temp.avg_areas = 0;
DFS(u);
temp.avg_areas = temp.area / temp.members;
temp.sets = temp.sets / temp.members;
path.push_back(temp); //记录得到的家庭信息
}
}
}
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i++) {
int id, f, m, k, area, child_id;
scanf("%d %d %d %d", &id, &f, &m, &k);
vislist[i] = id; //这里我直接把树当作图来做了,我把父母都当作孩子。。。。尴尬
for(int i = 0; i< k; i++) { //并且构造成无向图,转化为连同块来做
scanf("%d", &child_id);
tree[id].child_id.push_back(child_id);
tree[child_id].child_id.push_back(id);
}
if(f != -1) {
tree[id].child_id.push_back(f);
tree[f].child_id.push_back(id);
}
if(m != -1) {
tree[id].child_id.push_back(m);
tree[m].child_id.push_back(id);
}
scanf("%d %d", &tree[id].sets, &tree[id].area);
}
DFSTrave();
sort(path.begin(), path.end(), cmp); //排序
printf("%d\n", path.size());
for(int i = 0; i < path.size(); i++) {
printf("%04d %d %.3lf %.3lf\n", path[i].min_id, path[i].members, path[i].sets, path[i].avg_areas);
}
return 0;
}
并查集解法:
思路,就是把所有的人都合并,看能划分几个块,每个块都是一个家族,然后在合并的时候动一下手脚,让id小的当族长。然后划分出来的家族家族中id小的是族长。
然后遍历一遍所有的结点,出现过的结点查询他的父亲,查到的父亲一定是他的族长,然后把资源合并到族长身上,最后,按照族长的资源排序。就行了。
代码附上:
#include<bits/stdc++.h>
using namespace std;
const int N = 10001;
const int INF = 0x3fffffff;
int Father[N];
bool vis[N];
struct famlly { //有用的功能就是记录一下家长的信息
int sets;
int areas;
famlly() {
areas = sets = 0;
}
}famlly_info[N];
struct node { //记录一下族长们的信息
int famlly_head_id;
double sets;
double areas;
int members;
bool is_famlly_head;
node() {
sets = areas = members = 0;
is_famlly_head = false;
}
}info[N];
vector<node> famlly_head; //家族信息 //从族长表筛选出族长方便排序输出,后面想了一下其实没必要直接排序族长表就行了。。。。
int Init() {
for(int i = 0; i < N; i++) {
Father[i] = i;
}
}
int Find_Father(int x) {
int a = x;
while(x != Father[x]) {
x = Father[x];
}
while(a != Father[a]) {
int z = a;
a = Father[a];
Father[z] = x; //路径压缩
}
return x;
}
void Union(int a, int b) {
int Fa = Find_Father(a);
int Fb = Find_Father(b);
if(Fa < Fb) { //谁小谁是爸爸
Father[Fb] = Fa;
} else {
Father[Fa] = Fb;
}
}
bool cmp(node a, node b) {
if(a.areas / a.members != b.areas / b.members) return a.areas / a.members > b.areas / b.members; //由于不想在结构体里面申请元素,就写成这样了。。。
else return a.famlly_head_id < b.famlly_head_id;
}
int main() {
int n;
scanf("%d", &n);
Init();
for(int i = 0; i < n; i++) {
int id, F_id, M_id, k, child_id, sets, areas;
scanf("%d %d %d %d", &id, &F_id, &M_id, &k);
vis[id] = true;
if(F_id != -1) { //这里把所有人按家族合并好了,并且让id最小的当上了族长哈哈哈哈!!!
Union(F_id, id);
vis[F_id] = true;
}
if(M_id != -1) { //这里总体来说就是把一家人全合并而且小的当族长
Union(M_id, id);
vis[M_id] = true;
}
for(int j = 0; j < k; j++) {
scanf("%d", &child_id);
Union(child_id, id);
vis[child_id] = true;
}
scanf("%d %d", &sets, &areas);
famlly_info[id].sets = sets; //财产记录在家长名下
famlly_info[id].areas =areas;
}
for(int i = 0; i < N; i++) { //查找族长,并且把财产总和到族长名下
if(vis[i] == true) {
int F = Find_Father(i);
info[F].areas += famlly_info[i].areas;
info[F].sets += famlly_info[i].sets;
info[F].members++;
info[F].is_famlly_head = true; //这个货是族长
}
}
for(int i = 0; i < N; i++) {
if(vis[i] == true && info[i].is_famlly_head == true) { //筛选出族长
info[i].famlly_head_id = i;
famlly_head.push_back(info[i]);
}
}
sort(famlly_head.begin(), famlly_head.end(), cmp);
printf("%d\n", famlly_head.size());
for(int i = 0; i < famlly_head.size(); i++) { //这里的可读性不高哈, 反正就这个意思,按照题目意思输出就完事了
printf("%04d %d %.3lf %.3lf\n", famlly_head[i].famlly_head_id, famlly_head[i].members, famlly_head[i].sets / famlly_head[i].members, famlly_head[i].areas / famlly_head[i].members);
}
return 0;
}