问题描述
海面上有一些船需要与陆地进行通信,需要在海岸线上布置一些基站。现将问题抽象为,在 x 轴上方,给出 N 条船的坐标 p 1 , p 2 , … , p N , p i = ( x i , y i ) , 0 ≤ y i ≤ d , 1 ≤ i ≤ N , p_1,p_2,…,p_N,p_i=(x_i,y_i),0≤y_i≤d,1≤i≤N, p1,p2,…,pN,pi=(xi,yi),0≤yi≤d,1≤i≤N,在 x 轴上安放的基站可以覆盖半径为 d 的区域内的所有点,问在 x 轴上至少要安放几个点才可以将 x 轴上方的点都覆盖起来。试设计一个算法求解该问题,并分析算法的正确性。
算法设计
将这些船按照 x 坐标从小到大进行排序。按顺序遍历排序过后的船,若当前船无法被基站覆盖,则沿 x 增加的方向建设一个新基站,使得该基站恰好能覆盖该船。
最优子结构性质
设船 P = { 1 , 2 , . . . , n } P = \{1, 2, ..., n\} P={1,2,...,n} 已按 x 坐标递增的顺序排序。设 A 是包括基站 1 的最优解,则 A’ 是除去基站 1 覆盖的船,剩余所有船构成的子问题 P‘ 的最优解,基站数 ∣ A ′ ∣ = ∣ A ∣ − 1 |A'| = |A| - 1 ∣A′∣=∣A∣−1。
A’ 中的基站能够覆盖所有的船,只需要证明 A‘ 的基站数是最少的。
假设有个更优的基站安排方案 B’,满足
∣
A
′
∣
>
∣
B
′
∣
|A'| > |B'|
∣A′∣>∣B′∣,那么原问题的最优解应该是
∣
B
′
∣
+
1
<
∣
A
′
∣
+
1
|B'| + 1 < |A'| + 1
∣B′∣+1<∣A′∣+1,与 A 是最优解矛盾。
因此,问题的最优解包含子问题的最优解。
贪心选择性
设船 P = { 1 , 2 , . . . , n } P = \{1, 2, ..., n\} P={1,2,...,n} 已按 x 坐标递增的顺序排序。
假设存在一个最优解 A,其中第一个基站的位置不是贪心算法选择的位置,即不是能覆盖第一艘船的最右边的位置。那么,我们可以将这个基站的位置向右移动,直到它到达能覆盖第一艘船的最右边的位置,这样不会减少它能覆盖的船的数量。因此,这样得到的解 B 也是一个最优解,且它的第一个基站的位置是贪心算法选择的位置。
设贪心算法选择的第一个基站的位置为 x,我们将这个基站覆盖的船从原问题中去掉,得到一个子问题。对于这个子问题,我们同样可以用上述的方式得出,贪心算法选择的第一个位置是一个最优解。不断进行上述流程,我们最终可以得到船的数量为 0 的最小规模的子问题,此时不需要放置任何基站,显然是最优解。
通过上述的归纳假设,我们可以得出,贪心算法可以得到这个子问题的最优解。
代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
typedef pair<int, int> PII;
int main()
{
int n, d;
while(cin >> n >> d && (n || d)) {
vector<PII> p(n);
int x, y;
for(int i = 0; i < n; i++) {
cin >> x >> y;
p[i] = {x, y};
}
sort(p.begin(), p.end());
int res = 0, t;
for(auto it : p) {
if(res == 0 || (it.first - t)*(it.first - t) + it.second*it.second >= d*d) {
res++;
t = it.first + sqrt(d*d - it.second*it.second);
}
}
cout << res << endl;
}
return 0;
}