紫书 例题 6-22
一、题目描述
有一个 1000 × 1000 的正方形场地。西南角为原点 (0, 0) ,东北角为 (1000, 1000) 。一共有 n 个敌人,第 i 个敌人的坐标为 (xi, yi) ,攻击半径为 ri 。你必须从西边界的任意位置出发,不进入任何敌人的攻击范围走到东边界。如果可以从多个位置进出,则使进出位置尽量靠北。
输入包含多组数据,每组数据开头一行只有一个数 n ,代表敌人的个数。接下来 n 行每行 3 个数 xi, yi, ri(空格分隔)分别代表第 i 个敌人的坐标和攻击半径。输出四个数:
x1 y1 x2 y2
代表进场坐标和退场坐标。
二、算法分析说明与代码编写指导
本指导是根据紫书和 https://blog.youkuaiyun.com/cFarmerReally/article/details/52124802 改写的。
如果没有一些相交(连通)的圆将场地分成左右两半,那么一定有一条符合要求的路径。否则,场地左右不连通,无法从西边界走到东边界。求这样的一组连通圆可以用 DFS 。
记 y1 =L ,y2 = R ,visited 表示每个圆的访问情况。
因为各个圆都可能是孤立的,所以我们需要对每个圆都做一次 DFS 。如果已经访问过,直接中止该层 DFS 并返回。每层 DFS 返回的结果代表是否有圆与下边界相交或相切。
DFS 函数中需要设定中止条件:如果圆 C 已访问,那么当 visited[c] == 1 时中止 DFS 。
两个圆相交或相切的充要条件是:连接两个圆心的线段长小于半径之和。
如果两个圆是相交的,那么它们连通,则递归一层 DFS 。每经过一个圆,就将其已访问标记记为 true 。如果圆 C 堵住了下边界,即 y[c] < r[c] ,那么返回 true 。如何判断一组圆从上边界一路到下边界彻底堵死了自西向东的通路呢?只要同时验证 DFS 返回 true 与 y[i] + r[i] >= 1000.0 (即第 i 个圆与上边界相交) 即可。这时候输出:
IMPOSSIBLE
每次 DFS 结束前,检查是否有圆与左右边界相交。如果有,那么能够进出场地的最北的坐标就需要修改。与东西边界相交的最南端的圆与两边界的两交点中靠下的交点为待求结果(紫书上这么说的,具体为什么我也不知道)。该点的坐标的求法由几何关系易得。
如果正在访问的圆没有与下边界相交,那么 DFS 函数最后要返回 false 。
三、AC 代码(0 ms):
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<bitset>
using namespace std;
#pragma warning(disable:4996)
double x[1000], y[1000], r[1000], L, R; bitset<1000> visited; bool result; unsigned short n;
inline bool cir_ints(const unsigned short& c, const unsigned short& d){
return sqrt(pow((x[c] - x[d]), 2) + pow((y[c] - y[d]), 2)) <= r[c] + r[d];
}
inline void chk_bound(const unsigned short& c) {
if (x[c] <= r[c])L = min(L, y[c] - sqrt(r[c] * r[c] - x[c] * x[c]));
if (x[c] + r[c] >= 1000.0)R = min(R, y[c] - sqrt(r[c] * r[c] - pow(1000 - x[c], 2)));
}
inline bool DFS_u_to_d(const unsigned short& c) {
if (visited[c] == true)return false;
visited[c] = true;
if (y[c] <= r[c])return true;
for (unsigned short d = 0; d < n; ++d) {
if (cir_ints(c, d) == true && DFS_u_to_d(d) == true)return true;
}
chk_bound(c); return false;
}
int main() {
for (;;) {
if (scanf("%hu", &n) == EOF)return 0;
L = 1000.0, R = 1000.0, result = true; visited.reset();
for (unsigned short i = 0; i < n; ++i)scanf("%lf%lf%lf", &x[i], &y[i], &r[i]);
for (unsigned short i = 0; i < n; ++i) {
if (y[i] + r[i] >= 1000.0 && DFS_u_to_d(i) == true) { result = false; break; }
}
switch (result) {
case true:printf("0.00 %.2lf 1000.00 %.2lf\n", L, R); continue;
case false:puts("IMPOSSIBLE");
}
}
}