[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; } ```