原题链接
- Online Judge: 10382 - Watering Grass
- Virtual Judge: Watering Grass - UVA 10382
- 洛谷:UVA10382 Watering Grass
- LibreOJ:#10002. 「一本通 1.1 例 3」喷水装置,题目稍有不同,数据组数确定为 T T T。
解决题目
先通过勾股定理求出每个圆向左向右实际能覆盖的距离 k k k,将题目中的圆转化为矩形。
可以看出来这是一道贪心问题。我这里使用的方法是从右往左覆盖草坪,并用 l a s t last last 标记当前覆盖到的位置。
每次循环中从左向右枚举右侧( e d ed ed)能到达当前位置 l a s t last last 的圆圈。标记出现的第一个符合要求的圆圈, l a s t last last 跳到该圆圈的左侧( s t st st)。因为这些圆圈已经按照左侧( s t st st)由大到小的顺序排序,因此枚举出来的一定是符合要求的最靠左侧的圆圈,覆盖草坪的速度最快,且不用考虑对之后选择的影响,因此一定最优。
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 10000;
struct node {
double st;
double ed;
bool operator < (const node &K) const {
return st < K.st;
//排序时 st 小的在前
}
}a[N + 5];
int n, cnt, l, w;
double ww;
int main() {
while (~scanf("%d%d%d", &n, &l, &w)) {
ww = double(w) / 2;
cnt = 0;
for (int i = 1; i <= n; i++) {
double x, y, k;
scanf("%lf%lf", &x, &y);
if (y <= ww) {
//如果连宽度也不能覆盖则跳过
continue;
}
cnt++;
k = sqrt(y * y - ww * ww);
//计算向左向右实际覆盖距离
a[cnt].st = x - k;
a[cnt].ed = x + k;
}
n = cnt;
sort(a + 1, a + n + 1);
double last = l;
int lastp = n + 1;
int ans = 0;
bool flag;
while (last > 0) {
flag = false;
for (int i = 1; i < lastp; i++) {
//只循环到上一次符合要求的圆圈所在位置
if (a[i].ed >= last && a[i].st < last) {
flag = true;
last = a[i].st;
lastp = i;
ans++;
break;
}
}
if (!flag) {
//没有找到符合要求的圆圈,无法完成浇灌
printf("-1\n");
break;
}
}
if (flag) {
printf("%d\n", ans);
}
}
return 0;
}
注意事项
- 题目是多组数据,组数不指定。我这里使用
while (~scanf("%d%d%d", &n, &l, &w))
,当读到文件结尾时终止循环; - 第 28 行,如果连宽度都不能覆盖则跳过;我刚开始时没有跳过,导致之后循环时加上了这些圆圈。会导致除样例外全错;
- 第 46 行我用了一个 l a s t p lastp lastp 变量,目的是让循环时只循环到上一次符合要求的圆圈,减少无法复制时的循环次数;
- 题目是多组数据,因此 c n t cnt cnt 等变量需要初始化,避免影响这次循环。
- LibreOJ 或一本通中的题目稍有不同,数据组数确定为 T T T。每次循环减去一并判断即可:
int T;
scanf("%d", &T);
while (T--) {
...
}