# APCS實作題2017年3月第2題:小群體 > 第1版:2023年2月14日 > 第2版:2023年6月10日,新增 C++ 程式碼 > 作者:王一哲 > 題目來源:[106年3月4日實作題第2題](https://apcs.csie.ntnu.edu.tw/wp-content/uploads/2018/12/1060304APCSImplementation.pdf) > [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=c291) <br /> ## 題目 ### 問題描述 Q 同學正在學習程式,P 老師出了以下的題目讓他練習。 一群人在一起時經常會形成一個一個的小群體。假設有 N 個人,編號由 0 到 N-1,每個人都寫下他最好朋友的編號(最好朋友有可能是他自己的編號,如果他自己沒有其他好友),在本題中,**每個人的好友編號絕對不會重複,也就是說 0 到 N-1 每個數字都恰好出現一次**。 這種好友的關係會形成一些小群體。例如 N = 10,好友編號如下 <center> | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | | ------ | - | - | - | - | - | - | - | - | - | - | | 好友編號 | 4 | 7 | 2 | 9 | 6 | 0 | 8 | 1 | 5 | 3 | </center> <br /> 0 的好友是 4,4 的好友是 6,6 的好友是 8,8 的好友是 5,5 的好友是 0,所以 0、4、6、8、和 5 就形成了一個小群體。另外,1 的好友是 7 而且 7 的好友是 1,所以 1 和 7 形成另一個小群體,同理,3 和 9 是一個小群體,而 2 的好友是自己,因此他自己是一個小群體。總而言之,在這個例子裡有 4 個小群體:{0, 4, 6, 8, 5}、{1, 7}、{3, 9}、{2}。本題的問題是:輸入每個人的好友編號,計算出總共有幾個小群體。 Q 同學想了想卻不知如何下手,和藹可親的 P 老師於是給了他以下的提示:如果你從任何一人 x 開始,追蹤他的好友,好友的好友,….,這樣一直下去,一定會形成一個圈回到 x,這就是一個小群體。如果我們追蹤的過程中把追蹤過的加以標記,很容易知道哪些人已經追蹤過,因此,當一個小群體找到之後,我們再從任何一個還未追蹤過的開始繼續找下一個小群體,直到所有的人都追蹤完畢。 Q 同學聽完之後很順利的完成了作業。 在本題中,你的任務與 Q 同學一樣:給定一群人的好友,請計算出小群體個數。 <br /> ### 輸入格式 第一行是一個正整數 N,說明團體中人數。 第二行依序是 0 的好友編號、1 的好友編號、……、N-1 的好友編號。共有 N 個數字,包含 0 到 N-1 的每個數字恰好出現一次,數字間會有一個空白隔開。 <br /> ### 輸出格式 請輸出小群體的個數。不要有任何多餘的字或空白,並以換行字元結尾。 <br /> ### 範例一:輸入 ``` 10 4 7 2 9 6 0 8 1 5 3 ``` ### 範例一:正確輸出 ``` 4 ``` (說明)4 個小群體是 {0, 4, 6, 8, 5}, {1, 7}, {3, 9}和{2}。 <br /> ### 範例二:輸入 ``` 3 0 2 1 ``` ### 範例二:正確輸出 ``` 2 ``` (說明)2 個小群體分別是{0}, {1, 2}。 <br /> ### 評分說明 輸入包含若干筆測試資料,每一筆測試資料的執行時間限制(time limit)均為 1 秒,依正確通過測資筆數給分。其中: - 第 1 子題組 20 分,$1 \leq N \leq 100$,每一個小群體不超過 2 人。 - 第 2 子題組 30 分,$1 \leq N \leq 1,000$,無其他限制 。 - 第 3 子題組 50 分,$1,001 \leq N \leq 50,000$,無其他限制。 <br /> ## Python 程式碼 ```python= N = int(input()) # 人數 friends = list(map(int, input().split())) # 好朋友資料 visited = [False]*N # 是否已經讀過這筆資料 count = 0 # 小群體數量 #group = [] # 小群體資料,二維串列,除錯用 for i in range(N): while not visited[i]: visited[i] = True #group.append([i]) if friends[i] == i: pass else: j = friends[i] visited[j] = True #group[-1].append(j) j = friends[j] while not visited[j]: visited[j] = True #group[-1].append(j) j = friends[j] count += 1 #print(group) print(count) ``` <br /> 1. 第7 ~ 22行:使用 for 迴圈將資料完整地跑過一次。 2. 第8 ~ 22行:於 for 迴圈中的 while 迴圈,如果這筆資料還沒有讀過時繼續運作。先將這筆資料的狀態改成已讀過。接下來有兩種可能的狀況: 1. 第11、12行:自己是一個小群體,pass,不做任何事。 2. 第13 ~ 21行:先找到 i 的朋友 j,將 j 的狀態改成已讀過;再找 j 的朋友,覆蓋掉 j 原來的值;使用 while 迴圈重複以上3行,直到讀過所有資料。如果使用 C 或 C++ 解題,可以使用 do while 迴圈,但是 Python 沒有 do while 迴圈,所以在 while 迴圈前先跑一次同樣的步驟。 外層的 while 迴圈執行一次,代表找到一個小群體中所有的成員,小群體數量 count 加1。 3. 第5、10、16、20、24行:這幾行是除錯用的工具,將小群體的成員儲存在二維串列 group,提交程式碼測試時要註解或刪除。如果測資為 ``` 10 4 7 2 9 6 0 8 1 5 3 ``` group 最後印出的內容為 ```python [[0, 4, 6, 8, 5], [1, 7], [2], [3, 9]] ``` 4. 於 ZeroJudge 測試結果,第10筆測資花費時間最久,約 61 ms,使用記憶體 9.2 MB,通過測試。 <br /><br /> ## C++ 程式碼 ```cpp= #include <iostream> using namespace std; int main(int argc, char* argv[]) { // 由標準輸入讀取好友資料筆數 N、好友資料 friends int N; cin >> N; int friends[N], visited[N] = {0}, count = 0; for(int i=0; i<N; i++) { cin >> friends[i]; } for(int i=0; i<N; i++) { while(visited[i] == 0) { visited[i] = 1; if (friends[i] == i) { ; } else { int j = friends[i]; do { visited[j] = 1; j = friends[j]; } while(visited[j] == 0); } count++; } } cout << count << endl; return 0; } ``` <br /> 1. 第6 ~ 11行:由標準輸入讀取讀取好友資料筆數 N、好友資料 friends。 2. 第13 ~ 27行:使用 for 迴圈將資料完整地跑過一次。 3. 第14 ~ 26行:於 for 迴圈中的 while 迴圈,如果這筆資料還沒有讀過時繼續運作。先將這筆資料的狀態改成已讀過。接下來有兩種可能的狀況: 1. 第16、17行:自己是一個小群體,不做任何事。 2. 第18 ~ 24行:使用 do while 迴圈,先找到 i 的朋友 j,將 j 的狀態改成已讀過;再找 j 的朋友,覆蓋掉 j 原來的值,直到讀過所有資料。 外層的 while 迴圈執行一次,代表找到一個小群體中所有的成員,小群體數量 count 加1。 4. 於 ZeroJudge 測試結果,花費時間為 39 ms,使用記憶體 704 kB,通過測試。 <br /><br /> --- ###### tags:`APCS`、`Python`、`C++`