範例程式碼已於 UVA
、瘋狂程設(CPE)
上皆測試通過。
題意
在二維平面上存在一些小島 i s l a n d s = [ x i , y i ] islands = [x_i, y_i] i s l an d s = [ x i , y i ] ,其中 ( x i , y i ) (x_i, y_i) ( x i , y i ) 表示第 i i i 個小島的座標。
現在要在 x軸 上安裝雷達偵測器,使得所有小島都能被雷達偵測到,且使得所需的雷達偵測器數量最少,問最少需要安裝多少雷達偵測器。
思路:排序 + 區間覆蓋
對於每個小島,首先可以求出放置雷達後,可以涵蓋這個島嶼的雷達其所屬的區間,即 [ x − d 2 − y 2 , x + d 2 − y 2 ] [x - \sqrt{d^2 - y^2}, x + \sqrt{d^2 - y^2}] [ x − d 2 − y 2 , x + d 2 − y 2 ] ,可以畫圖理解。
如此問題便可以轉換成:在數線上有若干個區間,使用最少的點,使得每個區間內都至少有一個點。
這是一個經典的區間覆蓋問題,可以對所有區間依照右端點進行排序,然後從左到右遍歷。
令 l a s t last l a s t 表示該組中第一個區間右端點,初始值為 − ∞ -\infty − ∞ 。
對於每個區間 [ l , r ] [l, r] [ l , r ] ,如果 l > l a s t l > last l > l a s t ,則表示這個區間和上一組中的第一個區間不重疊,因此需要一個新的點,將 r r r 設為新的 l a s t last l a s t 。
否則,這個區間與同組中的所有區間皆有重疊部分,因此不需要新的點。證明如下:
由於已經按照右端點排序,故 r > l a s t r > last r > l a s t ,這個區間與該組中的區間重疊的部分為 [ l , l a s t ] [l, last] [ l , l a s t ]
而該組中的所有區間,都包含了 [ l a s t , l a s t ] [last, last] [ l a s t , l a s t ] ,故可以放置一個點在 l a s t last l a s t 來使組內所有區間都存在至少一個點。
複雜度分析
時間複雜度:O ( n log n ) O(n \log n) O ( n log n ) ,其中 n n n 為小島的數量,每個小島會產生一個區間,故排序所有時間的的時間複雜度為 O ( n log n ) O(n \log n) O ( n log n ) 。
空間複雜度:O ( n ) O(n) O ( n ) ,存儲所有區間。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 tc = 1 while True : line = input () if tc > 1 : line = input () n, d = map (int , line.split()) if n == 0 and d == 0 : break islands = [] for _ in range (n): line = input () islands.append(list (map (int , line.split()))) flag = False intervals = [] for x, y in islands: if y > d: flag = True break dx = (d*d - y*y)**0.5 intervals.append((x - dx, x + dx)) intervals.sort(key = lambda x: x[1 ]) ans = 0 last = -float ("inf" ) for x, y in intervals: if x > last: last = y ans += 1 print (f"Case {tc} : {ans if not flag else -1 } " ) tc += 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <bits/stdc++.h> using namespace std;const int INF = 0x3f3f3f3f ;#define endl '\n' bool cmp (const pair<double , double >& a, const pair<double , double >& b) { return a.second < b.second; } int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); cout.tie (nullptr ); int n, d, tc = 1 , x, y; double dx; while (cin >> n >> d && n && d) { vector<pair<double , double >> intervals; bool flag = false ; for (int i = 0 ; i < n; ++i) { cin >> x >> y; if (y > d) flag = true ; dx = sqrt (d * d - y * y); intervals.push_back ({x - dx, x + dx}); } sort (intervals.begin (), intervals.end (), cmp); int ans = 0 ; double last = (double ) -INF; for (auto &i : intervals) { if (i.first > last) { last = i.second; ans++; } } cout << "Case " << tc++ << ": " << (flag ? -1 : ans) << endl; } return 0 ; }
類題:合併區間
寫在最後
Cover photo is remixed from @Tekeli_liw3 , thanks for their work!
只要能想到每個島嶼都有其能夠放置雷達的範圍區間,就是很典型的區間覆蓋問題。