原题地址:
http://poj.org/problem?id=1328
题意:将一条海岸线看成X轴,X轴上面是大海,海上有若干岛屿,给出岛屿的n个位置和雷达的覆盖半径d,要求在海岸线上建雷达,在雷达能够覆盖全部岛屿情况下,求雷达的最少使用量。
解题思路
这道题的提问方式来看,是很明显的贪心算法的应用,但和背包问题、性价比问题的贪心算法方式有所不同。
一开始想的是对所有岛屿的纵坐标降序排序,在排序后的点对应的横坐标上建雷达,尽可能覆盖这周边岛屿。后来觉得完全没有道理,只能推翻重来。
后来受到网上大神们的启发,利用贪心算法转化到平面几何+最少区间问题。具体的思路如下:
- 每个岛屿能被这种半径为d的雷达范围是有限的,这个范围是由以岛屿为圆心,半径为d的圆与x轴的交点决定的(交点个数为0代表不可能被感应到,个数为1或2都是可能被感应到的)。这两个交点的值left/right可以通过平面几何计算得到,那么在[left, right]范围内放置雷达都能感应到这个岛屿,因此这个范围内必须放置一个雷达。
- 贪心思想的体现就是让一个雷达的位置尽可能向右覆盖更多的区间。对所有区间的左界left升序排序,并依次对前后区间进行操作。用r来记录前一雷达可以摆放的最右界。
举例来说,对于已经记录的前一区间的右界r,如果当前区间的左界大于r,说明这两个区间没有交集,必须在第二个区间增加雷达,而这个新的r即第二个区间的右界;如果当前区间的左界大于r,说明这两个区间有交集,就不必增加新的雷达,新的r更新为min(r,第二个区间的右界)。
注意点
- 由于涉及到几何的平方、开方计算,因此中间变量都要声明为double
- 异常的情况如下:圆和x轴无交集、雷达范围无效、岛屿纵坐标为负
- 前几次提交时由于没有想清楚,在两区间有交集时,将r直接更新为新区间的右界,后来想想是很诡异的(这样雷达就无法保证前面的区间了),所以必须更新为两者的最小值,这样才能保证两者都被覆盖。
- G++超时,C++过了而且很快,浪费了我一个小时编译器你妹- -
AC代码
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#define MAXN 1005
using namespace std;
typedef struct node
{
double left, right;
bool operator < (const node &A) const //指定排序规则,以区间的左界升序
{
return left < A.left;
}
}Interval;
int main()
{
int n, kase = 0;
double d;
Interval cover[MAXN];
while (cin >> n >> d)
{
if (n == 0 && d == 0) break;
kase++;
double s, t;
bool possible = true;
for (int i = 0; i<n; ++i) //以每个岛屿为圆心画半径为d的圆,将它与x轴的交集记录到该岛屿所在区间
{
cin >> s >> t;
if (t > d || d <= 0 || t < 0) //不可能的情况:圆和x轴无交集、雷达无效、岛屿无效
{
possible = false;
continue;
}
//计算出岛屿可能被雷达侦测到的左右边界
double range = sqrt(d*d-t*t);
cover[i].left = s-range;
cover[i].right = s+range;
}
if (possible == false)
{
printf("Case %d: -1\n", kase);
continue; //处理下一个case
}
sort(cover, cover+n); //区间左界升序
int radar = 1;
double r = cover[0].right; //r记录前一雷达能摆放的最右界
for (int i = 1; i<n; ++i)
{
if (cover[i].left > r) //情况1:新区间超出前一雷达最右界
{
radar++; //在新区间放置雷达
r = cover[i].right; //更新最右界为新区间右界
}
else //情况2:新区间和前一雷达有交集(或包含)
r = min(r, cover[i].right);
}
printf("Case %d: %d\n", kase, radar);
}
return 0;
}
内存占用:256K 耗时:32ms (G++ TLE)
算法复杂度: O(nlogn)