题意简述
如图所示,x轴上方有n(1≤n≤1000)个岛屿(坐标均为整数), 在x轴上可以放置覆盖半径为d(整数)的雷达。问最少放置多少个雷达能够覆盖所有岛屿?如果无法全部覆盖则输出-1。
算法分析
这道题折磨了我好几天,一直WA一直WA…本来觉得没多难都被WA崩溃了…好了不扯了进正题。
首先分析无解的情况。容易得到:当且仅当某一岛屿的纵坐标大于雷达半径时,此组数据无解。 这个可以在读入时直接判断。需要注意的是,即便已经得到无解结论,也应等所有数据输入完毕再输出-1。
接下来就是求解过程了。
这题用贪心。但刚拿到手时,由于思考的局限性给出了错误的贪心策略。导致WA了好几次…(血泪史啊!T.T)下面先说错误的:
- 错误贪心策略:
- 所有岛屿按照横坐标从小到大排序。若横坐标相等则按纵坐标从大到小排序。
- 首先利用第一个岛屿坐标,选择能够覆盖住它的,在它右侧距离最远的x轴上的点,放下第一个雷达。(位置可由勾股确定)。如图所示:橘色为雷达位置。
- 扫描下一个点,如果在当前雷达覆盖范围内则继续向后扫描,否则以该点坐标为基准,依照步骤2中方法确立一个新雷达位置。直到所有岛屿被覆盖。
这个贪心策略至少我当时完全没有想到任何破绽…可是一直WA一直WA…直到学长给了这样一组数据:
其中雷达半径为5。这组数据只需在(1,0)放置一个雷达即可。但按照上面的策略,则需放置两个雷达。分别在(1,0)和(3,0)。
这让我相当郁闷……最后无奈上网找了其他人的AC程序,才找到正确的贪心策略。
正确贪心策略:
- 首先在读入时,用勾股算出x轴上能够覆盖住当前点的区间。
- 将区间按照左端点排序。
- 因为若两区间有包含关系,则小区间内存在雷达即可满足大区间需求。所以从右向左扫描所有区间,标记因包含关系无需处理的大区间。
- 从左向右扫描。首先判断该区间是否被标记。若区间需要处理,则判断当前雷达是否在区间内。若在则继续向下扫描。若不在,则选取区间右端点标记为新雷达位置。同时累加器自增。扫描结束后输出累加器值即可。
Problem Status: AC。时间0ms,内存392k
——————————————————————分割线——————————————————————
TIPS:
在思考这个正确的贪心策略时,发现网上的那个AC程序是这样实现去除包含关系的:
for (i = 0; i < n; i++) if (b[i] == 0) if (pos < area[i][0]) { ans++; pos = area[i][1]; }
b[i]是标记数组,pos表示当前雷达位置。area[i]是存储区间的数组。其中[0]存左端点,[1]存右端点。
这种方法其实有一个漏洞:无法去除两个左端点相同的区间的包含关系。例如:区间[1,2]和[1,3]无论其先后顺序,都不会被标记为存在包含的区间。
但如果继续考虑下面判断是否新建雷达的算法,则可以容易发现这个并不妨碍统计雷达个数。
可参照下面的程序样例进行理解。
——————————————————————分割线——————————————————————
为进行比对,下面将贴出错误贪心策略的代码及正确贪心策略的代码。
程序样例
错误贪心策略:
#include<stdio.h> #include<stdlib.h> #include<math.h> int comp(const void * a, const void * b) { int *c, *d; c = (int *) a; d = (int *) b; if ( *c > *d) return 1; if ( *c < *d) return -1; c++; d++; if (( *c) < ( *d)) return 1; return -1; } double dis(double pos, int x, int y) { double a, b, c; a = y * y; b = (x - pos) * (x - pos); return sqrt(a + b); } int main() { int n, d, i, p[1000][2], ans = 1, c = 0, b[1000] = { 1 }; double pos; scanf("%d%d", &n, &d); while (!(n == 0 && d == 0)) { c++; if (n == 0) { printf("Case %d: 0\n", c); scanf("%d%d", &n, &d); continue; } ans = 1; for (i = 0; i < n; i++) { scanf("%d%d", &p[i][0], &p[i][1]); if (p[i][2] > d) ans = -1; } if (ans == -1 || d == 0) { printf("Case %d: -1\n", c); scanf("%d%d", &n, &d); continue; } comp(p[0], p[1]); qsort(p, n, 2 * sizeof(int), comp); pos = p[0][0] + sqrt(d * d - p[0][1] * p[0][1]); for (i = 1; i < n; i++) if (dis(pos, p[i][0], p[i][1]) <= d * 1.0) continue; else { ans++; pos = p[i][0] + sqrt(d * d - p[i][1] * p[i][1]); } printf("Case %d: %d\n", c, ans); scanf("%d%d", &n, &d); } return 0; }
正确贪心策略:
#include<stdio.h> #include<stdlib.h> #include<math.h> int comp(const void * a, const void * b) { double * c, * d; c = (double * ) a; d = (double * ) b; if ( * c > * d) return 1; return -1; } int main() { int n, d, c = 0, p[1000][2], i, ans, b[1000] = {0}; double pos, area[1000][2], t; scanf("%d%d", & n, & d); while (!(n == 0 && d == 0)) { c++; ans = 0; for (i = 0; i < n; i++) { scanf("%d%d", & p[i][0], & p[i][1]); if (p[i][1] > d) ans = -1; else { area[i][0] = sqrt(d * d - p[i][1] * p[i][1]); area[i][1] = p[i][0] + area[i][0]; area[i][0] = p[i][0] - area[i][0]; } b[i] = 0; } if (ans == -1 || d == 0) { printf("Case %d: -1\n", c); scanf("%d%d", & n, & d); continue; } qsort(area, n, 2 * sizeof(double), comp); t = area[n - 1][1]; for (i = n - 2; i >= 0; i--) if (area[i][1] >= t) b[i] = 1; else t = area[i][1]; pos = -2100000000; for (i = 0; i < n; i++) if (b[i] == 0) if (pos < area[i][0]) { ans++; pos = area[i][1]; } printf("Case %d: %d\n", c, ans); scanf("%d%d", & n, & d); } return 0; }