题目大意
给定一张1000*1000的图,要求从左边走到右边,无论起点还是终点都要尽可能的向上。
再给定N组Ball,给出Ball的坐标(x,y)和其半径r,要求在从左往右走的过程不能进入Ball的范围中。
答案要求输出起点和终点坐标,如果无法从左走向右那么输出IMPOSSIBL。
思路
看起来是个比较平凡的BFS求最短路问题,但仔细分析后发现如果直接BFS+边走边判的时间复杂的是
10^9,是必然超时的。所以不能直接套BFS。既然正着思考(类似模拟–言听计从)行不通,不妨试试逆向思考。从
障碍物Ball出发,如果存在一条“踩着“Ball自上边界到下边界的路,相当于此张图被Ball分割成了两半,那
么此时就肯定不能从左往右走了,可以直接输出IMPOSSIBLE,否则就必然存在一条从左往右的路。说回那条Ball路,在这
条路上,每经过一个Ball就判断是否与左边界x=0有交点,如果有交点,则更新起点坐标的值,对于右边
界x=1000同样如此。要注意起终点纵坐标设为1000。关于如果判断Ball和边界是否有交点的实现细节需要用到一点平面几何知识:
直线y=ax+bx+c,圆(x-a)^2 + (y-b)^2 =r^2
点(a,b)到直线y的距离|d|=|ax+by+c|/sqrt(a^2 + b^2 ),如果d^2 <= r^2,则Ball与边界有交点。在更新坐标值时要求交点坐标:
将直线与圆方程联立(直线即左右边界x1=0或x1=1000),可得
y=sqrt(r^2 -(x1-a)^2)+b
虽然y值有两个,但需除去在区间[0,1000]范围之外的,然后再取较大的哪一个。
由于此BFS是在Ball上跑的而1<=Ball<=1000,并且每次还需判断边界和寻找下一个Ball,则时间复杂度是O(Ball^2 )为10^6。
反思
对于需要BFS操作时,首先要先push进一个出发点,然后再while(!q.empty())进行下面的操作,而再push出发点时不要忘记更新坐标值。类比初始化vis[i]=1。此外,此题的思路很有意思,启示我们不要思维定势。对于那条Ball路,多画几个图就能理解其正确性了。
代码
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
struct Ball{
int x,y,z;
};
Ball Circle[1005];
int n;
queue<Ball> q;
bool vis[1005];
bool dis(int x1,int y1,int r1,int x2,int y2,int r2)
{
if((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)<=(r1+r2)*(r1+r2))return true; //两点距离小于半径之和则两圆相交,等于则相切
return false;
}
bool Line_Circle(int a,int b,int c,int x,int y,int r) //边界线与圆相交
{
if(r*r-(a*x+b*y-c)*(a*x+b*y-c)>=0)return true; //点到线距离公式
return false;
}
bool BFS()
{
double left_y=1000,right_y=1000;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++){ //找与上边界相交或相切的圆
if(Line_Circle(0,1,1000,Circle[i].x,Circle[i].y,Circle[i].z)){
double r=(double)Circle[i].z,a=(double)Circle[i].x,b=(double)Circle[i].y;
if(Line_Circle(1,0,0,Circle[i].x,Circle[i].y,Circle[i].z)){
double k=sqrt(r*r-a*a);
if(k+b>=0&&k+b<=1000)left_y=min(left_y,k+b);
if(-k+b>=0&&-k+b<=1000)left_y=min(left_y,-k+b);
}
if(Line_Circle(1,0,1000,Circle[i].x,Circle[i].y,Circle[i].z)){
double k=sqrt(r*r-(1000-a)*(1000-a));
if(k+b>=0&&k+b<=1000)right_y=min(right_y,k+b);
if(-k+b>=0&&-k+b<=1000)right_y=min(right_y,-k+b);
}
q.push(Circle[i]);
vis[i]=1;
}
}
while(!q.empty()){
Ball a;
a=q.front();
q.pop();
if(Line_Circle(0,1,0,a.x,a.y,a.z)){ //圆与下边界有交点
return false;
}
for(int i=1;i<=n;i++){
if(!vis[i]&&dis(a.x,a.y,a.z,Circle[i].x,Circle[i].y,Circle[i].z)){
double r=(double)Circle[i].z,a=(double)Circle[i].x,b=(double)Circle[i].y;
if(Line_Circle(1,0,0,Circle[i].x,Circle[i].y,Circle[i].z)){
double k=sqrt(r*r-a*a);
if(k+b>=0&&k+b<=1000)left_y=min(left_y,k+b);
if(-k+b>=0&&-k+b<=1000)left_y=min(left_y,-k+b);
}
if(Line_Circle(1,0,1000,Circle[i].x,Circle[i].y,Circle[i].z)){
double k=sqrt(r*r-(1000-a)*(1000-a));
//printf("k=:%lf\n",k);
if(k+b>=0&&k+b<=1000)right_y=min(right_y,k+b);
if(-k+b>=0&&-k+b<=1000)right_y=min(right_y,-k+b);
}
q.push(Circle[i]);
vis[i]=1;
}
}
}
printf("0.00 %.2lf 1000.00 %.2lf\n",left_y,right_y);
return true;
}
int main()
{
//freopen("1.txt","r",stdin);
//freopen("2.txt","w",stdout);
while(scanf("%d",&n)!=EOF){
for(int i=1;i<=n;i++){
scanf("%d%d%d",&Circle[i].x,&Circle[i].y,&Circle[i].z);
}
if(BFS()) continue;
else printf("IMPOSSIBLE\n");
}
}