# APCS實作題2022年1月第4題:牆上海報
> 第1版:2024年8月15日
> 第2版:2024年12月15日,新增跳躍二分搜尋法程式碼
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=h084)
## 題目
### 問題描述
有一個由 $n$ 個木板所組成的柵欄,每個木板的高度為 $h_1, h_2, \dots, h_n$,有 $k$ 張海報要張貼在柵欄上,每張海報的寬度為 $w_1, w_2, \dots, w_n$ 並且高度均為 $1$。
若要張貼海報在高度為 $x$ 的高度,則第 $i$ 張海報需要張貼在一個寬度為 $w_i$ 的連續並且高度都不小於 $x$ 的木板上,且每張海報張貼的**高度需要一致、按照順序並不能重疊**,可以相連。詢問最高可以貼到多高的位置。
<br />
### 輸入說明
第一行有兩個正整數 $n, k$,接下來一行有 $n$ 個正整數代表每個木板的高度,最後一行有 $k$ 個正整數代表每張海報的寬度。
數字範圍
- $1 \leq n \leq 200000$
- $1 \leq k \leq 5000$
- $1 \leq h_i \leq 10^9$
- $\sum w_i \leq n$
子題配分
- 20%:$1 \leq n \leq 100,~ k = 1$
- 40%:$k = 1$
- 40%:無額外限制
<br />
### 輸出格式
輸入最大可以張貼的高度。
<br />
### 範例輸入1
```
5 1
6 3 7 5 1
3
```
### 範例輸出1
```
3
```
### 範例輸入2
```
10 3
5 3 7 5 1 7 5 3 8 4
2 2 1
```
### 範例輸出2
```
5
```
<br /><br />
## Python 程式碼
### 寫法1,二分搜尋法
費時最久約為 1.3 s,使用記憶體最多約為 28.5 MB,通過測試。
```python=
n, k = map(int, input().split()) # 讀取牆壁數量 n、海報數量 k
hs = list(map(int, input().split())) # 讀取牆壁高度
ws = list(map(int, input().split())) # 讀取海報寬度
def check(m): # 自訂函式,檢測指定高度是否能貼所有的海報
i = 0 # 讀取海報寬度的索引值
width = 0 # 連繼牆壁寬度
for h in hs: # 依序由 hs 讀取高度 h
if h >= m: width += 1 # 如果這片牆壁高度大於等於 m,width 加 1
else: width = 0; continue # 否則將 width 歸零,找下一片
if width == ws[i]: # 如果 width 等於目前的海報寬度
i += 1; width = 0 # i 加 1,width 歸零
if i == k: return True # 如果 i 等於 k,所有海報都能貼上,回傳 True
return False # 如果離開 for 迴圈,表示有海報無法貼上,回傳 False
low, high = 1, 1000000001 # 可能的答案最小值、最大值
while low <= high: # 繼續執行的條件
mid = (high - low) // 2 + low # 中間值
if check(mid): low = mid # 如果 mid 符合要求,提高 low,找更高的高度
else: high = mid # 否則降低 high,找更低的高度
print(low) # 輸出答案 low
```
<br />
如果改成 [low, high] 閉區間的寫法,答案會存在變數 high。費時最久約為 1.3 s,使用記憶體最多約為 28.5 MB,通過測試。
```python=
n, k = map(int, input().split()) # 讀取牆壁數量 n、海報數量 k
hs = list(map(int, input().split())) # 讀取牆壁高度
ws = list(map(int, input().split())) # 讀取海報寬度
def check(m): # 自訂函式,檢測指定高度是否能貼所有的海報
i = 0 # 讀取海報寬度的索引值
width = 0 # 連繼牆壁寬度
for h in hs: # 依序由 hs 讀取高度 h
if h >= m: width += 1 # 如果這片牆壁高度大於等於 m,width 加 1
else: width = 0; continue # 否則將 width 歸零,找下一片
if width == ws[i]: # 如果 width 等於目前的海報寬度
i += 1; width = 0 # i 加 1,width 歸零
if i == k: return True # 如果 i 等於 k,所有海報都能貼上,回傳 True
return False # 如果離開 for 迴圈,表示有海報無法貼上,回傳 False
low, high = 1, 1000000001 # 可能的答案最小值、最大值
while low <= high: # 繼續執行的條件
mid = (high - low) // 2 + low # 中間值
if check(mid): low = mid+1 # 如果 mid 符合要求,提高 low,找更高的高度
else: high = mid-1 # 否則降低 high,找更低的高度
print(high) # 輸出答案 high
```
<br />
### 寫法2,跳躍二分搜尋法
費時最久約為 1.4 s,使用記憶體最多約為 27.7 MB,通過測試。
```python=
n, k = map(int, input().split()) # 讀取牆壁數量 n、海報數量 k
hs = list(map(int, input().split())) # 讀取牆壁高度
ws = list(map(int, input().split())) # 讀取海報寬度
def check(m): # 自訂函式,檢測指定高度是否能貼所有的海報
i = 0 # 讀取海報寬度的索引值
width = 0 # 連繼牆壁寬度
for h in hs: # 依序由 hs 讀取高度 h
if h >= m: width += 1 # 如果這片牆壁高度大於等於 m,width 加 1
else: width = 0; continue # 否則將 width 歸零,找下一片
if width == ws[i]: # 如果 width 等於目前的海報寬度
i += 1; width = 0 # i 加 1,width 歸零
if i == k: return True # 如果 i 等於 k,所有海報都能貼上,回傳 True
return False # 如果離開 for 迴圈,表示有海報無法貼上,回傳 False
h, jump = 1, (1+1000000001)//2 # 可能的答案、跳躍距離
while jump: # 當 jump 大於 0 時繼續執行
if check(h+jump): h += jump # 如果 h+jump 符合要求,提高 h,找更高的高度
else: jump >>= 1 # 反之代表跳躍距離太遠,jump 減半
# 結束 while 迴圈時,h 為答案
print(h) # 輸出答案
```
<br /><br />
## C++ 程式碼
### 寫法1,二分搜尋法
費時最久約為 35 ms,使用記憶體最多約為 2.6 MB,通過測試。
```cpp=
#include <iostream>
#include <vector>
using namespace std;
bool check(int m, vector<int> hs, vector<int> ws) { // 自訂函式,檢測指定高度是否能貼所有的海報
int width = 0, i = 0; // 連繼牆壁寬度 width,讀取海報寬度的索引值 i
for(int h : hs) { // 依序由 hs 讀取高度 h
if (h >= m) { // 如果這片牆壁高度大於等於 m,width 加 1
width++;
} else { // 否則將 width 歸零,找下一片
width = 0;
continue;
}
if (width == ws[i]) { // 如果 width 等於目前的海報寬度
i++; width = 0; // i 加 1,width 歸零
}
if (i == (int)ws.size()) return true; // 如果 i 等於 k,所有海報都能貼上,回傳 true
}
return false; // 如果離開 for 迴圈,表示有海報無法貼上,回傳 false
}
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int n, k; cin >> n >> k; // 讀取牆壁數量 n、海報數量 k
vector<int> hs (n), ws (k);
for(int i=0; i<n; i++) cin >> hs[i]; // 讀取牆壁高度
for(int i=0; i<k; i++) cin >> ws[i]; // 讀取海報寬度
int low = 0, high = 1000000001; // 可能的答案最小值、最大值
while(high - low > 1) { // 繼續執行的條件
int mid = (high - low) / 2 + low; // 中間值
if (check(mid, hs, ws)) low = mid; // 如果 mid 符合要求,提高 low,找更高的高度
else high = mid; // 否則降低 high,找更低的高度
}
cout << low << "\n"; // 輸出答案 low
return 0;
}
```
<br />
如果改成 [low, high] 閉區間的寫法,答案會存在變數 high。費時最久約為 48 ms,使用記憶體最多約為 2.6 MB,通過測試。
```cpp=
#include <iostream>
#include <vector>
using namespace std;
bool check(int m, vector<int> hs, vector<int> ws) { // 自訂函式,檢測指定高度是否能貼所有的海報
int width = 0, i = 0; // 連繼牆壁寬度 width,讀取海報寬度的索引值 i
for(int h : hs) { // 依序由 hs 讀取高度 h
if (h >= m) { // 如果這片牆壁高度大於等於 m,width 加 1
width++;
} else { // 否則將 width 歸零,找下一片
width = 0;
continue;
}
if (width == ws[i]) { // 如果 width 等於目前的海報寬度
i++; width = 0; // i 加 1,width 歸零
}
if (i == (int)ws.size()) return true; // 如果 i 等於 k,所有海報都能貼上,回傳 true
}
return false; // 如果離開 for 迴圈,表示有海報無法貼上,回傳 false
}
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int n, k; cin >> n >> k; // 讀取牆壁數量 n、海報數量 k
vector<int> hs (n), ws (k);
for(int i=0; i<n; i++) cin >> hs[i]; // 讀取牆壁高度
for(int i=0; i<k; i++) cin >> ws[i]; // 讀取海報寬度
int low = 0, high = 1000000001; // 可能的答案最小值、最大值
while(low <= high) { // 繼續執行的條件
int mid = (high - low) / 2 + low; // 中間值
if (check(mid, hs, ws)) low = mid+1; // 如果 mid 符合要求,提高 low,找更高的高度
else high = mid-1; // 否則降低 high,找更低的高度
}
cout << high << "\n"; // 輸出答案
return 0;
}
```
<br />
### 寫法2,跳躍二分搜尋法
費時最久約為 37 ms,使用記憶體最多約為 2.6 MB,通過測試。
```cpp=
#include <iostream>
#include <vector>
using namespace std;
bool check(int m, vector<int> hs, vector<int> ws) { // 自訂函式,檢測指定高度是否能貼所有的海報
int width = 0, i = 0; // 連繼牆壁寬度 width,讀取海報寬度的索引值 i
for(int h : hs) { // 依序由 hs 讀取高度 h
if (h >= m) { // 如果這片牆壁高度大於等於 m,width 加 1
width++;
} else { // 否則將 width 歸零,找下一片
width = 0;
continue;
}
if (width == ws[i]) { // 如果 width 等於目前的海報寬度
i++; width = 0; // i 加 1,width 歸零
}
if (i == (int)ws.size()) return true; // 如果 i 等於 k,所有海報都能貼上,回傳 true
}
return false; // 如果離開 for 迴圈,表示有海報無法貼上,回傳 false
}
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int n, k; cin >> n >> k; // 讀取牆壁數量 n、海報數量 k
vector<int> hs (n), ws (k);
for(int i=0; i<n; i++) cin >> hs[i]; // 讀取牆壁高度
for(int i=0; i<k; i++) cin >> ws[i]; // 讀取海報寬度
int h = 1, jump = (1+1000000001)/2; // 可能的答案、跳躍距離
while(jump) { // 如果 jump 大於 0 繼續執行的條件
if (check(h+jump, hs, ws)) h += jump; // 如果 h+jump 符合要求,提高 h,找更高的高度
else jump >>= 1; // 反之代表跳躍距離太遠,jump 減半
}
cout << h << "\n"; // 輸出答案
return 0;
}
```
<br /><br />
---
###### tags:`APCS`、`Python`、`C++`