給定每個人的家庭成員和其自己名下的房產,請你統計出每個家庭的人口數、人均房產面積及房產套數。
輸入格式:
輸入第一行給出一個正整數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; }