# APCS實作題2025年1月第4題:分組開會 > 日期:2025年1月21日 > 作者:王一哲 > [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=q184) ## 題目 ### 問題描述 有 $n$ 個人在一個數線上,他們的位置座標分別為 $x_1, x_2, \dots, x_n$。今天要從 $n$ 個人中選出 $2k$ 個人開兩場會議,每一場會議要恰好 $k$ 個人參與,並且每一個人最多只能參與一個會議。 若一個人位在 $x$,欲前往 $y$ 處開會需要移動距離 $|x-y|$。請求出安排這兩場會議,使得參與會議的人移動距離總和最小值為何。 <br /> ### 輸入說明 第一行輸入兩個數字 $n, k~(2k \leq n \leq 2 \times 10^5)$,接下來輸入 $n$ 個非負整數,座標範圍不超過 $10^9$。 子題配分 - 30%:$n \leq 100$ - 70%:無限制 <br /> ### 輸出格式 輸出安排這兩場會議,使得參與會議的人移動距離總和最小值為何。 <br /> ### 範例輸入1 ``` 6 2 5 2 0 6 9 6 ``` ### 範例輸出1 ``` 2 ``` ### 範例輸入2 ``` 7 3 6 3 2 2 0 9 8 ``` ### 範例輸出2 ``` 4 ``` <br /><br /> ## 分析題目 本題的說明似乎不太清楚,看起來開會的地點應該是在某個人所在的位置,所以題目中的 $y$ 是 $x_1, x_2, \dots, x_n$ 之中的值。如果要取 $k$ 個人計算移動總距離最小值,則這 $k$ 個人一定在附近,集合地點一定在他們的中間。所以解題時先將 $x_1, x_2, \dots, x_n$ 由小到大排序。假設取 $x_i, x_{i+1}, \dots, x_{i+k-1}$ 計算移動總距離最小值,先找出中位數索引值 $$ mid = i + k//2 = i + m $$ 若中位數 $x_{mid} = x_{i+m}$,則中位數左側的人距離為 $x_{mid} - x_{i<mid}$,右側的人距離為 $x_{i>mid} - x_{mid}$,總距離為 $$ \begin{align*} d &= (x_{mid} - x_i) + (x_{mid} - x_{i+1}) + \dots + (x_{mid} - x_{mid-1})\\ &+ (x_{mid+1} - x_{mid}) + (x_{mid+2} - x_{mid}) + \dots + (x_{i+k-1} - x_{mid}) \\ &= -(x_i + x_{i+1} + \dots + x_{m-1}) + (x_{m+1} + x_{m+2} + \dots + x_{i+k-1}) \end{align*} $$ 如果用滑動視窗解題,固定視窗範圍為 $(left, right] = (left, left+k]$,$left$ 是左端出界的索引值,寬度固定為 $k$,則視窗滑動時,只要處理左、右兩端及中位數兩側的變化,將原來的總距離 $d$ 改為 $$ d + x_{left} - x_{left+m} - x_{left+k-m} + x_{left+k} $$ ## Python 程式碼 費時最久約為 0.9 s,使用記憶體最多約為 40 MB,通過測試。 ```python= from collections import deque n, k = map(int, input().split()) # 共有 n 個人,每場會議要有 k 個人 x = sorted(list(map(int, input().split()))) # 每個人的位置,由小到大排序 m = k//2 # k 的一半 curr = -sum(x[:m]) + sum(x[m:k]) # 編號 0 ~ k-1 的人移動的距離總和 if k%2 == 1: curr -= x[m] # 如果 k 是奇數,第4行會多加上 x[m],要扣掉 dp = [curr] # 以編號 k-1 ~ n-1 的人所在位置當作開會的位置,同組 k 個人移動的距離總和 minleft = ans = float('INF') # 第1組的最小值,答案 rhs = deque() # 第2組的最小值,可能會從左端出界 for left in range(n-k): # 依序取出界的左端點 left = 0 ~ n-k,找各種分組的距離,滑動視窗,寬度固定為 k curr = curr + x[left] - x[left+m] - x[left+k-m] + x[left+k] # 更新視窗內的移動總距離 dp.append(curr) # dp 加上新的移動總距離 if left >= k-1: # 開始可以取第2組 minleft = min(minleft, dp[left-k+1]) # 更新 minleft if left >= k: rhs.popleft() # 移除第2組左端出界的值 while rhs and curr <= rhs[-1]: rhs.pop() # 如果 rhs 還有資料而且 curr 小於等於 rhs 最後面的值,移除最後一項 rhs.append(curr) # curr 加入 rhs,最後一項一定是第2組的最小值 ans = min(ans, minleft + rhs[-1]) # 更新 ans print(ans) ``` <br /><br /> ## C++ 程式碼 費時最久約為 72 ms,使用記憶體最多約為 5.4 MB,通過測試。 ```cpp= #include <iostream> #include <vector> #include <deque> #include <algorithm> #include <climits> typedef long long LL; using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); int n, k; cin >> n >> k; // 共有 n 個人,每場會議要有 k 個人 vector<LL> x (n); // 每個人的位置,輸入之後由小到大排序 for(int i=0; i<n; i++) cin >> x[i]; sort(x.begin(), x.end()); int m = k/2; // k 的一半 LL curr = 0; // 範圍內的人移動的距離總和 for(int i=0; i<m; i++) curr -= x[i]; // 編號 0 ~ k-1 的人移動的距離總和 for(int i=m; i<k; i++) curr += x[i]; if (k%2 == 1) curr -= x[m]; // 如果 k 是奇數,第4行會多加上 x[m],要扣掉 vector<LL> dp = {curr}; // 以編號 k-1 ~ n-1 的人所在位置當作開會的位置,同組 k 個人移動的距離總和 LL minleft = LLONG_MAX, ans = LLONG_MAX; // 第1組的最小值,答案 deque<LL> rhs; // 第2組的最小值,可能會從左端出界 for(int left=0; left<n-k; left++) { // 依序取出界的左端點 left = 0 ~ n-k,找各種分組的距離,滑動視窗,寬度固定為 k curr = curr + x[left] - x[left+m] - x[left+k-m] + x[left+k]; // 更新視窗內的移動總距離 dp.push_back(curr); // dp 加上新的移動總距離 if (left >= k-1) { // 開始可以取第2組 minleft = min(minleft, dp[left-k+1]); // 更新 minleft if (left >= k) rhs.pop_front(); // 移除第2組左端出界的值 while(!rhs.empty() && curr <= rhs.back()) rhs.pop_back(); // 如果 rhs 還有資料而且 curr 小於等於 rhs 最後面的值,移除最後一項 rhs.push_back(curr); // curr 加入 rhs,最後一項一定是第2組的最小值 ans = min(ans, minleft + rhs.back()); // 更新 ans } } cout << ans << "\n"; return 0; } ``` <br /><br /> --- ###### tags:`APCS`、`Python`、`C++`