# APCS實作題2026年3月中高級第1題:比例分割
> 日期:2026年3月10日
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=s179)
## 題目
### 問題描述
小明有一個由 $n$ 個正整數組成的序列 $w_1, w_2, w_3, \dots, w_n$。他對序列中區間的「比例分割點」非常感興趣。
對於任何子區間 $[l, r]$,我們定義其區間和為 $S(l, r) = \sum_{i=l}^r w_i$,若 $l > r$,則 $S(l, r) = 0$。
現在小明有 $m$ 個詢問。每個詢問包含四個整數 $l, r, a, b$,請你找到一個唯一的分割點 $k$ ($l \leq k \leq r$),使得該點滿足以下兩個條件:
$$
\frac{S(l, k-1)}{S(l, r)} < \frac{a}{a+b} ~~~~~ \frac{S(l, k)}{S(l, r)} \geq \frac{a}{a+b}
$$
由於序列中的元素皆為**正整數**,可以保證對於每一組詢問,符合條件的 $k$ 恰好只有一個。
例如序列為 $2, 2, 6, 3, 1, 5$,當 $a = 1, b= 3$ 時,$k = 3$ 恰好會滿足條件。$S(1, 2) = 4, S(1, 3) = 10, S(1, 6) = 19$。
<br />
### 輸入說明
第一行包含兩個整數 $n, m$ ($1 \leq n, m \leq 10^5$),分別代表序列長度與詢問次數。
第二行包含 $n$ 個整數 $w_1, w_2, w_3, \dots, w_n$ ($1 \leq w_i \leq 10^4$)。
接下來的 $m$ 行,每行包含四個整數 $l, r, a, b$ ($1 \leq l \leq r \leq n, 1 \leq a \leq b \leq 10$)。
配分
- 40分:$1 \leq n, m \leq 10^3$
- 60分:無限制。
<br />
### 輸出格式
對於每個詢問,輸出一行包含一個整數 $k$,即滿足條件的分割點索引。
<br />
### 範例輸入1
```
6 1
2 2 6 3 1 5
1 6 1 1
```
### 範例輸出1
```
3
```
### 範例輸入2
```
12 3
1 4 2 3 6 7 1 9 9 8 10 6
4 10 1 6
1 6 1 10
3 11 6 8
```
### 範例輸出2
```
5
2
8
```
<br />
## Python 程式碼
我是用前綴和計算區間和,再對答案二分搜,速度不快,但是可以過關。費時最久約為 0.8 s,使用記憶體最多約為 15.8 MB,通過測試。
```python=
n, m = map(int, input().split()) # n 個數字,m 次詢問
psum = [0] + list(map(int, input().split())) # 暫存 n 個數字
for i in range(1, n+1): # 轉成前綴和
psum[i] += psum[i-1]
for _ in range(m): # m 次詢問的條件
le, ri, a, b = map(int, input().split())
sum_le_ri = psum[ri] - psum[le-1] # S(l, r)
ans = le # 答案
low, high = le, ri # 答案的下限、上限
while low <= high: # 對答案二分搜
mid = (high - low) // 2 + low
sum_le_mid_n1 = psum[mid-1] - psum[le-1] # S(l, k-1)
sum_le_mid = psum[mid] - psum[le-1] # S(l, k)
cond1 = sum_le_mid_n1 * (a+b) < sum_le_ri * a # 條件 1
cond2 = sum_le_mid * (a+b) >= sum_le_ri * a # 條件 2
if cond1 and cond2: # 條件 1、2 皆成立,找到答案
ans = mid
break
elif not cond1: # 條件 1 不成立,mid 太右邊,降低上限
high = mid - 1
else: # 條件 2 不成立,mid 太左邊,提升上限
low = mid + 1
print(ans)
```
用 sys.stdin.read() 一次讀取所有測資,費時最久約為 0.7 s,使用記憶體最多約為 34.8 MB,通過測試。
```python=
import sys
data = sys.stdin.read().split() # 一次讀取所有測資
n, m = map(int, data[:2]) # n 個數字,m 次詢問
ptr = 2
psum = [0] + list(map(int, data[ptr:ptr+n])) # 暫存 n 個數字
ptr += n
for i in range(1, n+1): # 轉成前綴和
psum[i] += psum[i-1]
for _ in range(m): # m 次詢問的條件
le, ri, a, b = map(int, data[ptr:ptr+4])
ptr += 4
sum_le_ri = psum[ri] - psum[le-1] # S(l, r)
ans = le # 答案
low, high = le, ri # 答案的下限、上限
while low <= high: # 對答案二分搜
mid = (high - low) // 2 + low
sum_le_mid_n1 = psum[mid-1] - psum[le-1] # S(l, k-1)
sum_le_mid = psum[mid] - psum[le-1] # S(l, k)
cond1 = sum_le_mid_n1 * (a+b) < sum_le_ri * a # 條件 1
cond2 = sum_le_mid * (a+b) >= sum_le_ri * a # 條件 2
if cond1 and cond2: # 條件 1、2 皆成立,找到答案
ans = mid
break
elif not cond1: # 條件 1 不成立,mid 太右邊,降低上限
high = mid - 1
else: # 條件 2 不成立,mid 太左邊,提升上限
low = mid + 1
print(ans)
```
<br /><br />
## C++ 程式碼
費時最久約為 25 ms,使用記憶體最多約為 1 MB,通過測試。
```cpp=
#include <cstdio>
#include <vector>
typedef long long LL;
using namespace std;
int main() {
LL n, m; // n 個數字,m 次詢問
scanf("%lld %lld", &n, &m);
vector<LL> psum (n+1, 0); // n 個數字,轉成前綴和
for(LL i=1; i<=n; i++) {
LL w; scanf("%d", &w);
psum[i] = psum[i-1] + w;
}
for(LL i=0; i<m; i++) { // m 次詢問的條件
LL le, ri, a, b;
scanf("%lld %lld %lld %lld", &le, &ri, &a, &b);
LL sum_le_ri = psum[ri] - psum[le-1]; // S(l, r)
LL ans = le, low = le, high = ri; // 答案,答案的下限、上限
while(low <= high) { // 對答案二分搜
LL mid = (high - low) / 2 + low;
LL sum_le_mid_n1 = psum[mid-1] - psum[le-1]; // S(l, k-1)
LL sum_le_mid = psum[mid] - psum[le-1]; // S(l, k)
bool cond1 = sum_le_mid_n1 * (a+b) < sum_le_ri * a; // 條件 1
bool cond2 = sum_le_mid * (a+b) >= sum_le_ri * a; // 條件 2
if (cond1 && cond2) { // 條件 1、2 皆成立,找到答案
ans = mid;
break;
} else if (!cond1) { // 條件 1 不成立,mid 太右邊,降低上限
high = mid - 1;
} else { // 條件 2 不成立,mid 太左邊,提升上限
low = mid + 1;
}
}
printf("%lld\n", ans);
}
return 0;
}
```
<br /><br />
---
###### tags:`APCS`、`Python`、`C++`