# APCS實作題2025年1月第3題:重組問題 > 日期:2025年1月14日 > 作者:王一哲 > [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=q183) ## 題目 ### 問題描述 對於一個長度為 $n$ 的陣列 $a_1, a_2, \dots, a_n$,已知 $a_1$ 為 $0$,定義 $\Delta$ 為陣列中任兩個數字的絕對值差所組成的一群數字。給定 $\Delta$ 的內容,輸出可以生成該 $\Delta$ 的最小與最大字典序陣列。 舉例來說,如果 $n = 3$ 且 $\Delta = (3, 4, 7)$,距離最大的是 $7$,因為最大距離必定是在最小與最大兩數之間,我們可以推論原序列的最大的數字是 $7$。接著,目前已知原序列有 $(0, 7)$,而距離序列扣除 $7$ 之後剩下 $(3, 4)$,我們要決定中間點的位置。此時最大的距離是 $4$,這個距離必然是該未知點與「最小的0」或者與「最大的7」之間的距離,所以這個點可能是 $4$ 或者 $3$,在檢驗距離序列後,發現兩者皆可能,所以原序列可能是 $(0, 3, 7)$ 或者 $(0, 4, 7)$,其中字典序最小的是 $(0, 3, 7)$,最大的是 $(0, 4, 7)$。 <br /> ### 輸入說明 第一行輸入一個正整數 $n~(1 \leq n \leq 25)$,接下來一行有 $\frac{n(n-1)}{2}$ 個正整數。 子題配分 - 30%:$n \leq 6$ - 70%:無限制 <br /> ### 輸出格式 第一行輸出能生成出該 $\Delta$ 的最小字典序的陣列,第二行輸出能生成出該 $\Delta$ 的最大字典序的陣列。 <br /> ### 範例輸入1 ``` 3 3 4 7 ``` ### 範例輸出1 ``` 0 3 7 0 4 7 ``` ### 範例輸入2 ``` 5 1 2 3 3 5 5 6 8 10 11 ``` ### 範例輸出2 ``` 0 1 3 6 11 0 5 8 10 11 ``` <br /><br /> ## Python 程式碼 基本上就是看[吳邦一教授的寫法](https://hackmd.io/@bangyewu/rJlvXNP81g#%E7%AC%AC%E5%9B%9B%E9%A1%8C-%E5%88%86%E7%B5%84%E9%96%8B%E6%9C%83-ZeroJudge-q184)再加上一些自己的註解,沒什麼原創性,就不設定成公開發佈了。 費時最久約為 0.1 s,使用記憶體最多約為 3.4 MB,通過測試。 ```python= n = int(input()) # 陣列共有 n 個數字,所有的數字都在 1 ~ 100 之間 diff = sorted(list(map(int, input().split()))) # 讀取陣列中任意兩個數的差值,由小到大排序 if n == 1: # 特例 print(0); print(0) # 印出兩個 0 exit(0) # 沒有錯誤訊息,離開程式 imax = [0]*n # 最大字典序的答案,預設為小於題目範圍的 n 個 0 imin = [1000]*n # 最小字典序的答案,預設為於題目範圍的 n 個 1000 last = diff[-1] # diff 之中最後一項,最大的差值 # 自訂函式 dfs,代入已經找到的數字串列 ps, 已知的差值串列 ds,這次要測試的數字 t def dfs(ps, ds, t): global imax, imin # 全域變數,這樣才能修改 imax 及 imin 的值 for p in ps: # 依序從 ps 之中讀取數字 p target = abs(t-p) # 要找的目標差值 if target not in ds: return # 如果 target 不在 ds 之中,跳出 dfs ds.remove(target) # 已找到 target,從 ds 之中移除 target # t 是符合要求的數字 ps.append(t) # 將 t 加入 ps if not ds: # 如果 ds 是空的,已經沒有要找的數字,結束 dfs ps.sort() # 將 ps 由小到大排序 imin = min(imin, ps) # 更新 imin imax = max(imax, ps) # 更新 imax return # 結束 dfs # 遞迴 t = ds[-1] # t 改成目前 ds 中最後一項 dfs(ps.copy(), ds.copy(), t) # 要用 ps[:] 或 ps.copy() 複製資料,才不會改到原來的串列 dfs(ps.copy(), ds.copy(), last-t) # last-t 是另一組解 return # 結束 dfs # end of dfs dfs([0], diff, last) # 呼叫 dfs 求解 print(*imin) # 印出答案 print(*imax) ``` <br /> 只找最小字典序的解,再將它反過來就是最大字典序的解。費時最久約為 26 ms,使用記憶體最多約為 3.4 MB,通過測試。 ```python= n = int(input()) # 陣列共有 n 個數字,所有的數字都在 1 ~ 100 之間 diff = sorted(list(map(int, input().split()))) # 讀取陣列中任意兩個數的差值,由小到大排序 if n == 1: # 特例 print(0); print(0) # 印出兩個 0 exit(0) # 沒有錯誤訊息,離開程式 imin = [1000]*n # 最小字典序的答案,預設為於題目範圍的 n 個 1000 last = diff[-1] # diff 之中最後一項,最大的差值 # 自訂函式 dfs,代入已經找到的數字串列 ps, 已知的差值串列 ds,這次要測試的數字 t def dfs(ps, ds, t): global imin # 全域變數,這樣才能修改 imin 的值 for p in ps: # 依序從 ps 之中讀取數字 p target = abs(t-p) # 要找的目標差值 if target not in ds: return False # 如果 target 不在 ds 之中,回傳 False 跳出 dfs ds.remove(target) # 已找到 target,從 ds 之中移除 target # t 是符合要求的數字 ps.append(t) # 將 t 加入 ps if not ds: # 如果 ds 是空的,已經沒有要找的數字,結束 dfs ps.sort() # 將 ps 由小到大排序 imin = ps # 更新 imin return True # 回傳 True 結束 dfs # 遞迴,要用 ps[:] 或 ps.copy() 複製資料,才不會改到原來的串列 t = ds[-1] # t 改成目前 ds 中最後一項 if dfs(ps.copy(), ds.copy(), last-t): # last-t 是另一組解 return True # 回傳 True 結束 dfs else: return dfs(ps.copy(), ds.copy(), t) # end of dfs dfs([0], diff, last) # 呼叫 dfs 求解 print(*imin) # 印出答案 for i in range(n-1, -1, -1): # 印出 imax print(last-imin[i], end="\n" if i == 0 else " ") ``` <br /><br /> ## C++ 程式碼 費時最久約為 19 ms,使用記憶體最多約為 368 kB,通過測試。 ```cpp= #include <iostream> #include <cstdlib> #include <vector> #include <algorithm> using namespace std; vector<int> imin (100), imax (100); // 最小、最大字典序的答案 int last; // 陣列中任意兩個數的差值之中最大的值 /* 自訂函式 dfs,代入已經找到的數字串列 ps, 已知的差值串列 ds,這次要測試的數字 t */ void dfs(vector<int> ps, vector<int> ds, int t) { // 不能改變傳入的陣列值,用傳值呼叫 for(int p : ps) { // 依序從 ps 之中讀取數字 p int target = abs(t - p); // 要找的目標差值 auto it = find(ds.begin(), ds.end(), target); // 在 ds 之中找 target if (it == ds.end()) return; // 如果 target 不在 ds 之中,跳出 dfs ds.erase(it); // 已找到 target,從 ds 之中移除 target } ps.push_back(t); // t 是符合要求的數字,將 t 加入 ps if (ds.empty()) { // 如果 ds 是空的,已經沒有要找的數字,結束 dfs sort(ps.begin(), ps.end()); // 將 ps 由小到大排序 if (ps < imin) imin = ps; // 更新 imin if (ps > imax) imax = ps; // 更新 imax return; } t = ds.back(); // t 改成目前 ds 中最後一項 dfs(ps, ds, last-t); // 遞迴,last-t 是另一組解 dfs(ps, ds, t); // 遞迴,t 是另一組解 return; } int main() { ios::sync_with_stdio(0); cin.tie(0); int n; cin >> n; // 陣列共有 n 個數字,所有的數字都在 1 ~ 100 之間 if (n == 1) { // 特例 cout << "0\n0\n"; // 印出兩個 0 return 0; // 離開程式 } int m = n*(n-1)/2; // 差值數量 vector<int> diff (m); // 陣列中任意兩個數的差值 for(int i=0; i<m; i++) cin >> diff[i]; sort(diff.begin(), diff.end()); // 由小到大排序 imin.resize(n); imax.resize(n); // 調整 imin 及 imax 的大小 fill(imin.begin(), imin.end(), 1000); // 預設為於題目範圍的 n 個 1000 fill(imax.begin(), imax.end(), 0); // 預設為小於題目範圍的 n 個 0 vector<int> points = {0}; // 答案中一定有 0 last = diff.back(); // diff 之中最後一項,最大的差值 dfs(points, diff, last); // 呼叫 dfs 求解 for(int i=0; i<n; i++) cout << imin[i] << " \n"[i == n-1]; // 印出答案 for(int i=0; i<n; i++) cout << imax[i] << " \n"[i == n-1]; return 0; } ``` <br /> 只找最小字典序的解,再將它反過來就是最大字典序的解。費時最久約為 2 ms,使用記憶體最多約為 368 kB,通過測試。 ```cpp= #include <iostream> #include <cstdlib> #include <vector> #include <algorithm> using namespace std; vector<int> imin (100); // 最小、最大字典序的答案 int last; // 陣列中任意兩個數的差值之中最大的值 /* 自訂函式 dfs,代入已經找到的數字串列 ps, 已知的差值串列 ds,這次要測試的數字 t */ bool dfs(vector<int> ps, vector<int> ds, int t) { // 不能改變傳入的陣列值,用傳值呼叫 for(int p : ps) { // 依序從 ps 之中讀取數字 p int target = abs(t - p); // 要找的目標差值 auto it = find(ds.begin(), ds.end(), target); // 在 ds 之中找 target if (it == ds.end()) return false; // 如果 target 不在 ds 之中,回傳 false 跳出 dfs ds.erase(it); // 已找到 target,從 ds 之中移除 target } ps.push_back(t); // t 是符合要求的數字,將 t 加入 ps if (ds.empty()) { // 如果 ds 是空的,已經沒有要找的數字,結束 dfs sort(ps.begin(), ps.end()); // 將 ps 由小到大排序 imin = ps; // 更新 imin return true; // 回傳 true 結束 dfs } t = ds.back(); // t 改成目前 ds 中最後一項 if (dfs(ps, ds, last-t)) return true; // 遞迴,last-t 是另一組解 else return dfs(ps, ds, t); // 遞迴,t 是另一組解 } int main() { ios::sync_with_stdio(0); cin.tie(0); int n; cin >> n; // 陣列共有 n 個數字,所有的數字都在 1 ~ 100 之間 if (n == 1) { // 特例 cout << "0\n0\n"; // 印出兩個 0 return 0; // 離開程式 } int m = n*(n-1)/2; // 差值數量 vector<int> diff (m); // 陣列中任意兩個數的差值 for(int i=0; i<m; i++) cin >> diff[i]; sort(diff.begin(), diff.end()); // 由小到大排序 imin.resize(n); // 調整 imin 的大小 fill(imin.begin(), imin.end(), 1000); // 預設為於題目範圍的 n 個 1000 vector<int> points = {0}; // 答案中一定有 0 last = diff.back(); // diff 之中最後一項,最大的差值 dfs(points, diff, last); // 呼叫 dfs 求解 for(int i=0; i<n; i++) cout << imin[i] << " \n"[i == n-1]; // 印出答案 for(int i=n-1; i>=0; i--) cout << last-imin[i] << " \n"[i == 0]; return 0; } ``` <br /> --- ###### tags:`APCS`、`Python`、`C++`