[CODEFORCES 1253E Antenna Coverage](https://codeforces.com/contest/1253/problem/E)
=
## 題目大意
$[1, m]$ 數線上有 $n$ 根天線
每根天線位置 $x$ 有其範圍 $s$,將涵蓋 $[x-s, x+s]$ 區間
將範圍過擴大 $k$ 範圍會花費 $k$ 使涵蓋變為 $[x-s-k, x+s+k]$ 區間
目標是將 $[1, m]$ 都涵蓋
## 輸入
$n$ 與 $m$
及 $n$ 對 $x$ 和 $s$
## 輸出
最小花費
## 解法 1
由於需要決策使哪些天線成長,並且評估**最小**的花費
考慮看看這是不是個 DP 問題
細看問題,須將涵蓋的範圍遍及每個點,而每個點都知道被哪根天線覆蓋
> 但對我們而言是哪根並不重要
先試試粗暴的定義狀態 $\text{dp}(i)$ 就是涵蓋 $[1, i]$ 範圍的最小成本
若是有這個狀態,再配合一根天線區間會到 $j$ 的,那麼也能直接求得 $\text{dp}(j)$ 的解
而明顯的,一但 $\text{dp}(i)$ 被解出來,$\forall j>i.\text{dp} (j)$ 的解並不干擾 $\text{dp}(i)$
接著設計狀態轉移,
對於**固定**點 $i$,$\text{dp}(i)$ 僅需顧好範圍 $[1, i]$
看極端例子,若 $s = 0$,則只看 $x$ 的位置在哪就好
<!-- 若 $x$ 在 $i$ 的右側,花費 $i$
> 因為天線跑出 $[1, i]$ 違反題目條件,但為求解較 $i$ 大的問題,需要這個花費 -->
- 若 $x$ 在 $i$ 的左側,花費 $i - x$ 再加上 $x - 1$
- 若 $x$ 在 $i$ 上,花費 $x - 1$
> $-1$ 表示扣除天線本身位置
而 $s > 0$ 的話,就看 $[x-s, x+s]$ 這整個範圍是落在 $i$ 的左側還是裡面
- 在左側,花費為 $i-(x + s)$ 再加上 $\max(x - s - 1, 0)$
- 在裡面,花費為 $\max(x - s - 1, 0)$
> $x - s - 1$ 有可能會跑到範圍 $1$ 之外
靠 $\text{dp}(i)$ 要解出 $\forall j>i.\text{dp} (j)$,最差的狀況就是沒有天線預設涵蓋 $\text{dp}(i)$,所以初始化為 $i$
> 可以想像有天線涵蓋 $j$ 但不涵蓋 $i$ 的狀況,那麼花費就是 $i$ 到 $x-s$ 加上 $\text{dp}(i)$
```cpp
#include<bits/stdc++.h>
using namespace std;
int const maxn = 90;
int n, m, x[maxn], s[maxn];
int main()
{
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) scanf("%d%d", &x[i], &s[i]);
vector<int> dp(m+1);
iota(dp.begin(), dp.end(), 0);
for(int i = 1; i <= m; i++) {
for(int j = 0; j < n; j++) {
if(i > x[j]+s[j])
dp[i] = min(dp[i], dp[max(0, x[j]-(i-x[j])-1)] + i-(x[j]+s[j]));
else
dp[i] = min(dp[i], dp[max(0, x[j]-s[j]-1)]);
}
}
printf("%d\n", dp[m]);
return 0;
}
```
## 解法 2
$\text{dp}(i)$ 的狀態定義與解法 1 一樣
但在計算上,在遍歷到點 $i$ 上時會以早已解出 $\text{dp}(i)$ 為前提做狀態轉移
且事先加上已知的解 $\text{dp}(0) = 0$,為了那些預設已經涵蓋到 $i=1$ 的天線
```cpp
#include<bits/stdc++.h>
using namespace std;
int const maxn = 90;
int n, m, x[maxn], s[maxn];
int main()
{
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) scanf("%d%d", &x[i], &s[i]);
vector<int> idx(n);
iota(idx.begin(), idx.end(), 0);
sort(idx.begin(), idx.end(), [&](int a, int b) {
if(x[a] == x[b]) return s[a] < s[b];
return x[a] < x[b];
});
vector<int> dp(m+1);
iota(dp.begin(), dp.end(), 0);
for(int j: idx) {
for(int i = 0; i <= m; i++) {
int g = max(0, x[j]-s[j]-1 - i); // gap
int r = min(m, x[j]+s[j]+g); // right
dp[r] = min(dp[r], dp[i]+g);
}
}
int best = m-1;
for(int i = 1; i <= m; i++) best = min(best, dp[i] + m-i);
printf("%d\n", best);
return 0;
}
```
## 解法 3
- 若一支天線與另一支天線的範圍互相重疊或碰觸,那麼可將他們看作**一支範圍更大的天線**
- 而對於一支天線,花成本升級就能看作產生另**一支範圍更大的天線**
也就是說只要將所有種**覆蓋整個街道的天線**產生,看**哪個成本低**就行了
>如何有效率的產出覆蓋整個街道的天線?
街道從左至右,每遇到一個天線,就看他範圍內有沒有別的天線能碰到 $1$ 位置,如果有就將他跟這個天線合成為一支範圍更大的天線
而明顯的,眾多選擇中只要每次挑的天線成本最低,那麼合成的天線一定成本低
這樣一路合成天線到最右端 $m$,就能得到成本最低且覆蓋整個街道的天線。
實作採用線段樹,因為複雜度較小,細節請參考標準程式
```cpp
#include<bits/stdc++.h>
using namespace std;
int const maxn = 1e5 + 10;
int const INF = 0x3f3f3f3f;
int n, m;
vector<pair<int, int>> ant[maxn];
struct node {
int cost;
node *lc, *rc;
node() { cost = 0; lc = rc = NULL; }
};
node* build(int L, int R) {
node *u = new node();
if(L != R) {
int M = (L+R) / 2;
u->lc = build(L, M);
u->rc = build(M+1, R);
}
return u;
}
int query(node *u, int L, int R, int qL, int qR) {
if(R < qL || qR < L) return INF;
if(qL <= L && R <= qR) return u->cost;
int M = (L+R) / 2;
return min(query(u->lc, L, M, qL, qR), query(u->rc, M+1, R, qL, qR));
}
int update(node *u, int L, int R, int i, int cost) {
if(i < L || R < i) return u->cost;
if(L == R) return u->cost = cost;
int M = (L+R) / 2;
return u->cost = min(update(u->lc, L, M, i, cost), update(u->rc, M+1, R, i, cost));
}
int main()
{
scanf("%d%d", &n, &m);
while(n--) {
int x, s;
scanf("%d%d", &x, &s);
if(x-s <= 1 && m <= x+s) { puts("0"); return 0; }
for(int cost = 0; 1 <= x-s || x+s <= m; s++, cost++) // 將此天線能升級出的所有天線產出
ant[min(x+s, m)].emplace_back(max(x-s, 1), cost);
}
node *root = build(1, m);
for(int i = 1; i <= m; i++) {
int best = INF;
for(auto [qL, cost]: ant[i]) // 範圍 [qL, i] 的天線成本為 cost
best = min(best, query(root, 1, m, qL, i) + cost); // 選擇範圍中有重疊或碰觸的成本最小天線
if(i == m) printf("%d\n", best);
update(root, 1, m, i+1, best); // 若其他天線範圍有 i+1 則能與這個成本為 best 的天線合成
}
return 0;
}
```