# APCS實作題2021年11月第4題:真假子圖 > 日期:2024年8月27日 > 作者:王一哲 > [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=g598) <br /> ## 題目 ### 問題描述 情報調查局內有 $n$ 個工作人員, 調查局負責人將這些人秘密分成兩組 $A$ 和 $B$ 並不讓其他人知道, 並將合作名單分配給組長, 合作名單是由很多個 pair 組成, 每個 pair $(x, y)$ 代表 $x$ 和 $y$ 需要合作完成任務, 並且保證 $x$ 和 $y$ 不會同時在 $A$ 組或是同時在 $B$ 組。 組長不小心將這個合作名單分配遺失, 僅剩下其中 $m$ 個 pair, 為了要復原這些失去的資料, 組長派出了另外 $p$ 個調查員編號 $1$ 到 $p$ 去調查這個合作關係, 每一個調查員都會回傳恰好 $k$ 個 pair 的資料回來。 有些調查員回傳的資料和組長手上的資料會產生矛盾 (意即加上這 $k$ 個 pair 和組長手上存留的 $m$ 個 pair 會使得這些人是被分成 $A$, $B$ 兩組這件事產生矛盾), 請將回傳錯誤結果的調查員編號由小到大輸出出來, 保證至少一個且最多三個。 另外保證若調查員的 $k$ 個 pair 的結果和組長存留的 $m$ 個 pair 不會產生矛盾, 則保證調查員的資料一定和原本 $A$, $B$ 分組吻合。 <br /> ### 輸入格式 第一行先輸出兩個正整數 $n$ 和 $m$。 第二行有 $2m$ 個非負整數兩兩形成一個數對,表示目前還留存的 $m$ 個 pair。 第三行有兩個正整數 $p$ 和 $k$。 並且接下來的 $p$ 行每行有 $2k$ 個非負整數, 兩兩形成一對代表某個調查員找到的 $k$ 個 pair。 數字範圍 - $1 \leq n, m \leq 20000$ - $2 \leq p \leq 10000,~ 1 \leq k \leq 20$ - 每一個人被 pair 提到的次數 $\leq 150$ 子題配分 - 20%:$1 \leq n, m \leq 100,~ 1 \leq p \leq 20,~ ans = 1$ - 20%:$1 \leq n, m \leq 5000,~ 1 \leq p \leq 200$ - 60%:無額外限制 <br /> ### 輸出格式 由小到大輸出會形成矛盾的調查員編號,每個編號各自獨立一行。 <br /> ### 範例輸入1 ``` 7 5 0 1 0 2 1 3 2 3 4 5 2 3 0 6 2 4 3 6 0 6 0 3 3 5 ``` ### 範例輸出1 ``` 2 ``` ### 範例輸入2 ``` 5 2 0 3 2 3 3 2 0 2 2 4 0 1 1 2 3 4 2 4 ``` ### 範例輸出2 ``` 1 3 ``` <br /> ## Python 程式碼 費時最久約 3.4 s,使用記憶體最多 41 MB,通過測試。 ```python= def BFS(G, color, s): # 廣度優先走訪,輸入圖 G、顏色 color、起點 s color[s] = 1 # 指定起點顏色為 1 que = [s] # 要走訪的節點,先放入起點 s head = 0 # 從 que 讀資料用的索引值 while head < len(que): u = que[head]; head += 1 # 從 que 取出節點 u,head 加 1 for v in G[u]: # 依序取出與 u 相連的節點 if color[v] == color[u]: # 如果顏色相同,不是二分圖 return False # 回傳 Fasle if color[v] == 0: # 節點 v 未走訪 color[v] = -color[u] # 填上另一種顏色 que.append(v) # 將 v 加入 que return True # 結束 while 迴圈,是二分圖,回傳 True def bipar(G, num): # 檢測是否為二分圖 bipartite graph,相連節點要是不同的顏色 color = [0]*num # 兩種顏色 -1, 1,0 代表未檢測 for i in range(num): # 有 n 個點要檢測 if color[i] == 0: # 如果這個點未檢測 if not BFS(G, color, i): # 用 BFS 檢測 return False # 回傳 Fasle return True # 結束 for 迴圈,是二分圖,回傳 True n, m = map(int, input().split()) # 共有 n 對,目前剩下 m 對 d = list(map(int, input().split())) # 目前剩下的配對資料 graph = [[] for _ in range(n)] # 無向圖 for i in range(0, 2*m, 2): # 將目前剩下的配對資料填入圖中 graph[d[i]].append(d[i+1]) graph[d[i+1]].append(d[i]) p, k = map(int, input().split()) # p 個調查員,各回傳 k 對資料 gi = [] # 暫存調查員回傳資料的串列 for _ in range(p): # 讀取 p 行資料 gi.append(list(map(int, input().split()))) st = [[0, p-1]] # 要檢測的範圍 ans = [] # 儲存答案用的串列 while st: # 如果 st 還有資料,繼續執行 left, right = st.pop() # 由 st 取出左、右端點 Graph = [e.copy() for e in graph] # 複製 graph 的資料,不能直接用 graph.copy() for i in range(left, right+1): # 檢測 [left, right] 範圍 for j in range(0, 2*k, 2): # 由 gi 讀取配對資料加入 Graph Graph[gi[i][j]].append(gi[i][j+1]) Graph[gi[i][j+1]].append(gi[i][j]) if not bipar(Graph, n): # 如果不是二分圖 if left == right: ans.append(left+1) # 如果左、右端點相等,找到答案,將編號 left+1 加入 ans else: mid = (right - left) // 2 + left # 取中點 st.append([left, mid]) st.append([mid+1, right]) # 結束 while 迴圈 ans.sort() # 由小到大排序 for x in ans: print(x) # 印出答案 ``` <br /><br /> ## C++ 程式碼 費時最久約 0.3 s,使用記憶體最多 6.7 MB,通過測試。 ```cpp= #include <iostream> #include <vector> #include <stack> #include <utility> #include <algorithm> using namespace std; /* 廣度優先走訪,輸入圖 G、顏色 color、起點 s */ bool BFS(vector<vector<int>>& G, vector<int>& color, int s) { color[s] = 1; // 指定起點顏色為 1 stack<int> st; st.push(s); // 要走訪的節點,先放入起點 s while(!st.empty()) { // 如果 st 還有節點繼續執行 int u = st.top(); st.pop(); // 從 st 最上面取出並移除節點 for(int v : G[u]) { // 依序取出與 u 相連的節點 if (color[v] == color[u]) return false; // 如果顏色相同,不是二分圖,回傳 fasle if (color[v] == 0) { // 節點 v 未走訪 color[v] = -color[u]; // 填上另一種顏色 st.push(v); // 將 v 加入 st } } } return true; // 結束 while 迴圈,是二分圖,回傳 true } /* 檢測是否為二分圖 bipartite graph,相連節點要是不同的顏色 */ bool bipar(vector<vector<int>>& G, int num) { vector<int> color (num, 0); // 兩種顏色 -1, 1,0 代表未檢測 for(int i=0; i<num; i++) { // 有 n 個點要檢測 if (color[i] == 0) // 如果這個點未檢測 if (!BFS(G, color, i)) // 用 BFS 檢測,如果不是二分圖 return false; // 回傳 Fasle } return true; // 結束 for 迴圈,是二分圖,回傳 True } int main() { ios::sync_with_stdio(0); cin.tie(0); // 加速 int n, m; cin >> n >> m; // 共有 n 對,目前剩下 m 對 vector<vector<int>> graph (n, vector<int> ()); // 無向圖 for(int i=0; i<m; i++) { // 將目前剩下的配對資料填入圖中 int a, b; cin >> a >> b; graph[a].push_back(b); graph[b].push_back(a); } int p, k; cin >> p >> k; // p 個調查員,各回傳 k 對資料 vector<vector<int>> gi (p, vector<int> (2*k)); // 暫存調查員回傳資料的串列 for(int i=0; i<p; i++) { // 讀取 p 行資料 for(int j=0; j<2*k; j+=2) { cin >> gi[i][j] >> gi[i][j+1]; } } stack<pair<int, int>> st; // 要檢測的範圍 st.push(make_pair(0, p-1)); // 先放入 {0, p-1} vector<int> ans; // 儲存答案用的陣列 while(!st.empty()) { // 如果 st 還有資料,繼續執行 int left = st.top().first, right = st.top().second; st.pop(); // 由 st 最上面取出並移除左、右端點 vector<vector<int>> Graph (graph.begin(), graph.end()); // 複製 graph 的資料 for(int i=left; i<=right; i++) { // 檢測 [left, right] 範圍 for(int j=0; j<2*k; j+=2) { // 由 gi 讀取配對資料加入 Graph Graph[gi[i][j]].push_back(gi[i][j+1]); Graph[gi[i][j+1]].push_back(gi[i][j]); } } if (!bipar(Graph, n)) { // 如果不是二分圖 if (left == right) ans.push_back(left+1); // 如果左、右端點相等,找到答案,將編號 left+1 加入 ans else { int mid = (right - left) / 2 + left; // 取中點 st.push(make_pair(left, mid)); st.push(make_pair(mid+1, right)); } } } sort(ans.begin(), ans.end()); // 由小到大排序 for(auto a : ans) cout << a << "\n"; // 印出答案 return 0; } ``` <br /><br /> ## 參考資料 吳邦一教授的 APCS 解題影片〈[PyAp45 Python APCS 題解:病毒演化(AP202007_4), ZeroJudge f582](https://youtu.be/CzUCI2d1ER0?si=0PlLXicX4uIZeKSU)〉 --- ###### tags:`APCS`、`Python`、`C++`