# APCS實作題2017年3月第4題:基地台
> 第1版:2023年2月17日
> 第2版:2023年6月14日,加上 C++ 程式碼
> 作者:王一哲
> 題目來源:[106年3月4日實作題第4題](https://apcs.csie.ntnu.edu.tw/wp-content/uploads/2018/12/1060304APCSImplementation.pdf)
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=c575)
<br />
## 題目
### 問題描述
為因應資訊化與數位化的發展趨勢,某市長想要在城市的一些服務點上提供無線網路服務,因此他委託電信公司架設無線基地台。某電信公司負責其中 $N$ 個服務點,這 $N$ 個服務點位在一條筆直的大道上,它們的位置(座標)係以與該大道一端的距離 $P[i]$ 來表示,其中 $i = 0 \rightarrow N-1$。由於設備訂製與維護的因素,每個基地台的服務範圍必須都一樣,當基地台架設後,與此基地台距離不超過 $R$ (稱為基地台的半徑)的服務點都可以使用無線網路服務,也就是說每一個基地台可以服務的範圍是 $D = 2R$ (稱為基地台的直徑)。現在電信公司想要計算,如果要架設 $K$ 個基地台,那麼基地台的最小直徑是多少才能使每個服務點都可以得到服務。
基地台架設的地點不一定要在服務點上,最佳的架設地點也不唯一,但本題只需要求最小直徑即可。以下是一個 $N = 5$ 的例子,五個服務點的座標分別是 1、2、5、7、8。
<img height="80%" width="80%" src="https://i.imgur.com/ttu6ybw.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<br />
假設 $K = 1$,最小的直徑是 7,基地台架設在座標 4.5 的位置,所有點與基地台的距離都在半徑 3.5 以內。假設 $K = 2$,最小的直徑是 3,一個基地台服務座標 1 與 2 的點,另一個基地台服務另外三點。在 $K = 3$ 時,直徑只要 1 就足夠了。
<br />
### 輸入格式
輸入有兩行。第一行是兩個正整數 $N$ 與 $K$,以一個空白間格。第二行 $N$ 個非負整數$P[0]$,$P[1]$,….,$P[N-1]$ 表示 $N$ 個服務點的位置,這些位置彼此之間以一個空白間格。請注意,這 $N$ 個位置並不保證相異也未經過排序。本題中,$K < N$ 且所有座標是整數,因此,所求最小直徑必然是不小於 1 的整數。
<br />
### 輸出格式
輸出最小直徑,不要有任何多餘的字或空白並以換行結尾。
<br />
### 範例一:輸入
```
5 2
5 1 2 8 7
```
### 範例一:正確輸出
```
3
```
<br />
### 範例二:輸入
```
5 1
7 5 1 2 8
```
### 範例二:正確輸出
```
7
```
<br />
### 評分說明
輸入包含若干筆測試資料,每一筆測試資料的執行時間限制(time limit)均為 2 秒,依正確通過測資筆數給分。其中:
- 第 1 子題組 10 分,座標範圍不超過 100,$1 \leq K \leq 2$,$K < N \leq 10$。
- 第 2 子題組 20 分,座標範圍不超過 1,000,$1 \leq K < N \leq 100$。
- 第 3 子題組 20 分,座標範圍不超過 1,000,000,000,$1 \leq K < N \leq 500$。
- 第 4 子題組 50 分,座標範圍不超過 1,000,000,000,$1 \leq K < N \leq 50,000$。
<br />
## Python 程式碼
以下的程式碼說明已經寫在註解當中。於 ZeroJudge 測試結果,第16筆測資花費時間最久為 0.6 s,第14筆測資使用記憶體最多為 9.3 MB,通過測試。
<br />
```python=
# 由標準輸入讀取服務點數量 N、基地台數量 K、服務點位置 P
N, K = map(int, input().split())
P = list(map(int, input().split()))
P.sort() # P 由小到大排序
# 自訂函式 test,測試輸入的直徑 dis 是否能在 K 的基地台數量下涵蓋所有服務點位置
def test(dis):
count = 0 # 已使用基地台數量
i = 0 # 服務點位置索引值
while i < N: # 當服務點位置還沒讀完時繼續執行
pos = P[i] + dis # 這個服務點右側距離為 dis 的位置 pos
count += 1 # 已使用基地台數量加 1
if count > K: return False # 若已使用基地台數量大於 K,無法涵蓋所有服務點,回傳 False
if P[-1] <= pos and count <= K: return True # 可以涵蓋所有服務點,回傳 True
i += 1 # 先將索引值加 1
while P[i] <= pos: i += 1 # 若 P[i] 已在基地台範圍內,i 再加 1,直到 P[i] 不在基地台範圍內
return False # 若服務點位置都讀完時回傳 False
if K == 1: print(P[-1] - P[0]) # 只有 1 個基地台,基地台直徑為最右側、最左側服務點的距離
else: # 其它狀況
L = 1 # 基地台半徑最小值為 1
R = int((P[-1] - P[0]) / K) + 1 # 基地台半徑可能的最大值,一定可以涵蓋所有服務點
while L < R: # L 小於 R 時繼續執行
M = int((L+R)/2) # 計算中間值 M
if test(M): R = M # 呼叫自訂函式 test,代入 M 測試,若 M 可以涵蓋所有服務點,將最大值 R 重設為 M
else: L = M + 1 # 若 M 無法涵蓋所有服務點,將最小值 L 重設為 M + 1
print(R)
```
<br /><br />
## C++ 程式碼
以下的程式碼說明已經寫在註解當中。於 ZeroJudge 測試結果,第17筆測資花費時間最久為 97 ms,第16筆測資使用記憶體最多為 480 kB,通過測試。
<br />
```cpp=
#include <iostream>
#include <algorithm>
using namespace std;
bool test(int, int*);
int N, K;
int main(int argc, char* argv[]) {
// 由標準輸入讀取服務點數量 N、基地台數量 K、服務點位置 P
cin >> N >> K;
int P[N];
for(int i=0; i<N; i++) cin >> P[i];
sort(P, P+N); // P 由小到大排序
//for(int i=0; i<N; i++) cout << P[i] << " "; // 除錯用,印出排序後的服務點位置 P
//cout << endl;
if (K == 1) {
cout << P[N-1] - P[0] << endl; // 只有 1 個基地台,基地台直徑為最右側、最左側服務點的距離
} else { // 其它狀況
int L = 1; // 基地台半徑最小值為 1
int R = int((P[N-1] - P[0]) / K) + 1; // 基地台半徑可能的最大值,一定可以涵蓋所有服務點
while(L < R) { // L 小於 R 時繼續執行
int M = int((L+R)/2); // 計算中間值 M
if (test(M, P)) R = M; // 呼叫自訂函式 test,代入 M 測試,若 M 可以涵蓋所有服務點,將最大值 R 重設為 M
else L = M + 1; // 若 M 無法涵蓋所有服務點,將最小值 L 重設為 M + 1
}
cout << R << endl;
}
return 0;
}
// 自訂函式 test,測試輸入的直徑 dis 是否能在 K 的基地台數量下涵蓋所有服務點位置
bool test(int dis, int* P) {
int count = 0; // 已使用基地台數量
int i = 0; // 服務點位置索引值
while(i < N) { // 當服務點位置還沒讀完時繼續執行
int pos = P[i] + dis; // 這個服務點右側距離為 dis 的位置 pos
count++; // 已使用基地台數量加 1
if (count > K) return false; // 若已使用基地台數量大於 K,無法涵蓋所有服務點,回傳 false
if (P[N-1] <= pos && count <= K) return true; // 可以涵蓋所有服務點,回傳 True
i++; // 先將索引值加 1
while(P[i] <= pos) i++; // 若 P[i] 已在基地台範圍內,i 再加 1,直到 P[i] 不在基地台範圍內
}
return false; // 若服務點位置都讀完時回傳 False
}
```
<br /><br />
## 參考資料
黃建庭(2019)。**C++程式設計解題入門 融入程式設計競賽與APCS實作題(第二版)**。臺北市:碁峰資訊。 6-20
<br />
---
###### tags:`APCS`、`Python`