# APCS實作題2021年1月第3題:切割費用
> 日期:2024年7月10日
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=f607)
## 題目
### 問題描述
有一根長度為 $L$ 的棍子,你會把這個棍子切割 $n$ 次。
假設一開始棍子左端放在數線上 $0$ 的位置,棍子的右端放在數線上 $L$ 的位置,每次的切割會給定一個介於 $0$ 到 $L$ 的數字表示要切個的位置,你要把穿過個這位置的棍子切成兩段,而所需的花費就等於所切割的棍子的長度。
<br />
### 輸入說明
第一行有兩個整數 $n, L$。
接下來 $n$ 行每行有兩個整數 $x, i$,表示 $x$ 位置被切過一刀,而這刀是全部的切割中的第 $i$ 刀,保證 $i$ 是介於 $[1, n]$ 的整數且不會重複。
配分
- 20分:$1 \leq n \leq 1000,~ 1 \leq L \leq 10^7$
- 30分:$1 \leq n \leq 50000,~ 1 \leq L \leq 10^7$
- 50分:$1 \leq n \leq 200000,~ 1 \leq L \leq 10^7$
<br />
### 輸出格式
輸出一個整數表示總共的切割費用,答案可能超過 $2^{31}$ 但不會超過 $2^{60}$。
<br />
### 範例輸入
```
3 7
2 2
3 1
5 3
```
### 範例輸出
```
14
```
<br />
## Python 程式碼
### 寫法1,由 x 向左、右掃過找已切割的點
第10筆測資會超時。
```python=
n, L = map(int, input().split()) # 讀取切割次數 n、棍子長度 L
data = [] # 儲存資料用的容器
for _ in range(n): # 讀取 n 行資料,用數組 (x, i) 放入串列 data
data.append(tuple(map(int, input().split())))
data.sort() # 依照 x 由小到大排序
ans = 0 # 答案,預設為 0
for j, (x, i) in enumerate(data): # 依序讀取 data 的資料,索引值為 j
left = 0 # x 的左側已切割位置,預設為 0
right = L # x 的右側已切割位置,預設為 L
for k in range(j-1, -1, -1): # 由 j-1 開始向左掃過
if data[k][1] < i: # 如果 data[k] 已切割
left = data[k][0] # 找到 left
break
for k in range(j+1, n): # 由 j+1 開始向右掃過
if data[k][1] < i: # 如果 data[k] 已切割
right = data[k][0] # 找到 right
break
ans += right - left # 更新 ans
print(ans) # 印出 ans
```
<br /><br />
### 寫法2,使用 bisect 函式庫
費時最久約為 1.3 s,使用記憶體最多約為 32.9 MB,通過測試。
```python=
from bisect import bisect_left, insort
n, L = map(int, input().split()) # 讀取切割次數 n、棍子長度 L
data = [] # 儲存資料用的容器
for _ in range(n): # 讀取 n 行資料,用數組 (i, x) 放入串列 data
x, i = map(int, input().split())
data.append((i, x))
data.sort() # 依照 i 由小到大排序
pos = [0, data[0][1], L] # 已切割的位置,包含兩端 0、L
ans = L # 跳過第1個切割點,答案預設為 L
for i, x in data[1:]: # 依序讀取 data[1] ~ data[-1]
insort(pos, x) # 引用 bisect_insort,於 pos 中插入 x
j = bisect_left(pos, x) # 於 pos 中找 x 的索引值
left = pos[j-1] # x 左側切割點
right = pos[j+1] # x 右側切割點
ans += right - left # 更新 ans
print(ans) # 印出 ans
```
修改一下第14、15行,刪掉 insort,少做一次二分搜尋法,但是花費時間相差不多,最久約為 1.2 s,使用記憶體最多約為 32.9 MB,通過測試。
```python=
from bisect import bisect_left, insort
n, L = map(int, input().split()) # 讀取切割次數 n、棍子長度 L
data = [] # 儲存資料用的容器
for _ in range(n): # 讀取 n 行資料,用數組 (i, x) 放入串列 data
x, i = map(int, input().split())
data.append((i, x))
data.sort() # 依照 i 由小到大排序
pos = [0, data[0][1], L] # 已切割的位置,包含兩端 0、L
ans = L # 跳過第1個切割點,答案預設為 L
for i, x in data[1:]: # 依序讀取 data[1] ~ data[-1]
j = bisect_left(pos, x) # 於 pos 中找插入 x 的索引值
pos.insert(j, x) # 於 pos 中插入 x
left = pos[j-1] # x 左側切割點
right = pos[j+1] # x 右側切割點
ans += right - left # 更新 ans
print(ans) # 印出 ans
```
<br /><br />
## C++ 程式碼
### 寫法1,由 x 向左、右掃過找已切割的點
程式運作邏輯與 Python 程式碼寫法1相同。儲存 n, L, ans 的變數格式如果使用 int,從第10筆測資開始會溢位,改用 long long 才能通過測試。費時最久約為 0.5 s,使用記憶體最多約為 3.4 MB,通過測試。
```cpp=
#include <iostream>
#include <algorithm>
#include <utility>
typedef long long LL;
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0); // 加速
LL n, L; cin >> n >> L; // 讀取切割次數 n、棍子長度 L
pair<LL, LL> data[n]; // 儲存資料用的容器
for(LL i=0; i<n; i++) cin >> data[i].first >> data[i].second; // 讀取 n 行資料,用 pair (x, i) 放入陣列 data
sort(data, data+n); // 依照 x 由小到大排序
LL ans = 0; // 答案,預設為0
for(LL j=0; j<n; j++) { // 依序讀取 data 的資料,索引值為 j
LL x = data[j].first, i = data[j].second; // 第i次切割點位置 x
LL left = 0, right = L; // x 的左側已切割位置,預設為 0;x 的右側已切割位置,預設為 L
for(LL k=j-1; k>=0; k--) { // 由 j-1 開始向左掃過
if (data[k].second < i) { // 如果 data[k] 已切割
left = data[k].first; // 找到 left
break;
}
}
for(LL k=j+1; k<n; k++) { // 由 j+1 開始向右掃過
if (data[k].second < i) { // 如果 data[k] 已切割
right = data[k].first; // 找到 right
break;
}
}
ans += right - left; // 更新 ans
}
cout << ans << "\n"; // 印出 ans
return 0;
}
```
<br /><br />
### 寫法2,使用 lower_bound
程式運作邏輯與 Python 程式碼寫法2相同。儲存 n, L, ans 的變數格式如果使用 int,從第10筆測資開始會溢位,改用 long long 才能通過測試。費時最久約為 0.1 s,使用記憶體最多約為 7.9 MB,通過測試。
```cpp=
#include <iostream>
#include <algorithm>
#include <utility>
#include <vector>
typedef long long LL;
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0); // 加速
LL n, L; cin >> n >> L; // 讀取切割次數 n、棍子長度 L
pair<LL, LL> data[n]; // 儲存資料用的容器
for(LL i=0; i<n; i++) cin >> data[i].second >> data[i].first; // 讀取 n 行資料,用 pair (i, x) 放入陣列 data
sort(data, data+n); // 依照 i 由小到大排序
vector<LL> pos = {0, data[0].second, L}; // 切割點位置,包含兩端 0、L
LL ans = L; // 略過第1個切割點,答案預設為L
for(LL j=1; j<n; j++) { // 依序讀取 data[1] ~ data[n-1] 的資料,索引值為 j
LL i = data[j].first, x = data[j].second; // 第i次切割點位置 x
auto it = lower_bound(pos.begin(), pos.end(), x); // 找到於 pos 中插入 x 的位址
pos.insert(it, x); // 於 pos 中插入 x
LL idx = lower_bound(pos.begin(), pos.end(), x) - pos.begin(); // 找到 x 於 pos 中的索引值
LL left = pos[idx-1], right = pos[idx+1]; // x 的左、右兩側已切割位置
ans += right - left; // 更新 ans
}
cout << ans << "\n"; // 印出 ans
return 0;
}
```
```cpp=
#include <iostream>
#include <algorithm>
#include <utility>
#include <vector>
typedef long long LL;
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0); // 加速
LL n, L; cin >> n >> L; // 讀取切割次數 n、棍子長度 L
pair<LL, LL> data[n]; // 儲存資料用的容器
for(LL i=0; i<n; i++) cin >> data[i].second >> data[i].first; // 讀取 n 行資料,用 pair (i, x) 放入陣列 data
sort(data, data+n); // 依照 i 由小到大排序
vector<LL> pos = {0, data[0].second, L}; // 切割點位置,包含兩端 0、L
LL ans = L; // 略過第1個切割點,答案預設為L
for(LL j=1; j<n; j++) { // 依序讀取 data[1] ~ data[n-1] 的資料,索引值為 j
LL i = data[j].first, x = data[j].second; // 第i次切割點位置 x
LL idx = lower_bound(pos.begin(), pos.end(), x) - pos.begin(); // 找到於 pos 中插入 x 的位址
pos.insert(pos.begin()+idx, x); // 於 pos 中插入 x
LL left = pos[idx-1], right = pos[idx+1]; // x 的左、右兩側已切割位置
ans += right - left; // 更新 ans
}
cout << ans << "\n"; // 印出 ans
return 0;
}
```
<br /><br />
---
###### tags:`APCS`、`Python`、`C++`