题意: 有个1000*1000的正方形战场,战场西南角的坐标为(0,0),西北角的坐标为(0,1000)。战场上有n(0≤n≤1000)个敌人,第i个敌人的坐标为(xi,yi),攻击范围ri。为了避开敌人的攻击,在任意时刻,你与每个敌人的距离都必须严格大于它的攻击范围。你的任务是从战场的西边(x=0的某个点)进入,东边(x=1000的某个点)离开。如果有多个位置可以进出,你应当求出最靠北的位置。输入每个敌人的xi,yi,ri,输出进入战场和离开战场的坐标。
【分析】
本题初看起来比较麻烦,不妨把它简化下: 先判断是否有解,再考虑如何求出最靠北的位置。首先,可以把每个敌人抽象成一个圆, 圆心就是他所在位置, 半径是攻击范围,则本题变成了:正方形内有n个圆形障碍物,是否能从左边界走到右边界?
下一步需要一点创造性思维:把正方形战场看成一个湖,障碍物看成踏脚石,如果可以从上边界“走”到下边界,沿途经过的障碍物就会把湖隔成左右两半,相互无法到达,即本题无解,另一方面,如果从上边界走不到下边界,虽然仍然可能会出现某些封闭区域(图6-18中灰色区域),但一定可以从左边界的某个地方到达右边界的某个地方,如图6-18所示。
这样, 解的存在性只需要一次DFS或BFS判连通即可。如何求出最北的进出位置呢 ? 方法如下:这些圆和左边界的交点中最靠南边的个就是所求的最北进入位置和右边的最南交点就是所求的最北离开位置。
以上内容来自《算法竞赛入门经典》
补充:其实就是判断是否有一个连通块把这个长方形隔成了左右两部分,如果有就是没解,我们没有固定的方向可以走,应该怎么写呢,如果两个圆相交了那么他们两个就是一个联通块,我们找跟上边界相交的圆,以他为起点开始搜索,搜跟他相交的圆,如果有一个跟下边界相交了,那么就是没答案。给个图辅助理解吧:
这好像是是什么对偶图,不明白什么是对偶图,BZOJ的第一题狼抓兔子好像就是对偶图什么的,有空再学吧。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 10005;
const double W = 1000.0;
int n;
double x[MAXN], y[MAXN], r[MAXN], L, R;
bool ok, vis[MAXN];
bool intersect(int c1, int c2)//判断两个圆是否相交
{
return hypot(x[c1]-x[c2], y[c1]-y[c2]) <= r[c1]+r[c2];
}
void check_circle(int u)//更新答案
{
if (x[u] - r[u] <= 0.0)//更新左边答案
L = min(L, y[u] - sqrt(pow(r[u], 2) - pow(x[u], 2)));
if (x[u] + r[u] >= W)//更新右边答案
R = min(R, y[u] - sqrt(pow(r[u], 2) - pow(W-x[u], 2)));
}
bool dfs(int u)
{
if (vis[u]) return false;//已经走过
vis[u] = 1;
if (y[u] - r[u] <= 0) return true;//走到了最下面
for (int v = 0; v < n; v++)
if (intersect(u, v) && dfs(v)) return true;
check_circle(u);
return false;
}
int main()
{
while (scanf("%d", &n) == 1)
{
L = R = W;
ok = true;
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i++)
scanf("%lf%lf%lf", &x[i], &y[i], &r[i]);
for (int i = 0; i < n; i++)
{
if (y[i] + r[i] >= W && dfs(i)) { ok = false; break; }
}
if (ok) printf("%.2f %.2f %.2f %.2f\n", 0.0, L, W, R);//0.0写成0会wa
else printf("IMPOSSIBLE\n");
}
return 0;
}
/*
3
500 500 499
0 0 999
1000 1000 200
*/