题意:在三维空间里,给你球的圆心坐标,还有半径,再给你两个点 s 和 t ,问从 s 走到 t 的最段路程。题目保证, s 和 t 不在球的内部。
思路:先判断线段st是否和球相交,如果不相交的话直接输出线段st的长度即可;如果相交的话,易证:先从s到p1,再从p1沿着球面到p2,再从p2到t点即可最短路程。(其中p1为从s点到球的切点,p2为从t点到球的切点,并且p1和p2都在o、s、t这三个点确定的平面上,因为这样走的路程是最短的)
判断线段st是否和球相交,很容易想到计算球心到直线st的距离d,如果d>r,那么一定不相交;如果d<=r需要再分情况讨论,判断∠sto和∠tso是否同时为锐角,如果同时为锐角,那么线段和球相交,否则不相交。
也就是说,不能单纯的凭d和r的大小关系来判断线段st和球是否相交,因为可能存在直线st和球相交但线段st不和球相交的情况,这个画一下图就一目了然了。
而求d有很多种解法,一种是用海伦公式求出三角形ost的面积S,然后S/|st|即为d(不过这种解法好像精度不够,会wa2);另外一种是写出直线st的参数方程,列出方程解出参数就算出垂足了,然后求垂足和o点的距离即为d。还有,用ycy学长的模板判断直线和球是否相交也可以,详见代码。
训练的时候一直wa2,以为是精度问题,没想到是漏了上面这种情况,还是做题的时候对题目卡的不够死,没有完全弄清题......
ACcode:(用ycy学长的板子判断st是否和球相交)
#include <bits/stdc++.h>
using namespace std;
const double eps=1e-6;
const double pi=acos(-1.0);
double sqr(double x){return x*x;}
int sgn(double x){
return (x>eps)-(x<-eps);
}
struct point{
double x,y,z;
point(){}
point(double _x,double _y,double _z)
{
x=_x;y=_y;z=_z;
}
point operator+(point a){
return point(x+a.x,y+a.y,z+a.z);
}
point operator-(point a){
return point(x-a.x,y-a.y,z-a.z);
}
point operator*(point a){
return point(y*a.z-z*a.y,z*a.x-x*a.z,x*a.y-y*a.x);
}
point operator*(double t){
return point(x*t,y*t,z*t);
}
point operator/(double t){
return point(x/t,y/t,z/t);
}
double operator^(point a){
return x*a.x+y*a.y+z*a.z;
}
///
double vl()
{
return sqrt(x*x+y*y+z*z);
}
};
double dist(point l1,point l2)
{
return sqrt((l1.x-l2.x)*(l1.x-l2.x)+(l1.y-l2.y)*(l1.y-l2.y)+(l1.z-l2.z)*(l1.z-l2.z));
}
inline bool line_ball(point l1,point l2,point ori,double r,point &p1,point &p2)
{
double d=((l1-ori)*(l2-ori)).vl()/dist(l1,l2);
if(sgn(d-r)>=0)
return 0;
point nor;
if(sgn(d)==0)
nor=ori;
else
{
double k=-((l1-ori)^(l2-l1))/sqr((l2-l1).vl());
nor=(l2-l1)*k+l1;
}
if(sgn((l2.x-l1.x)*(nor.x-l1.x))<0||sgn(l2.y-l1.y)*(nor.y-l1.y)<0||(sgn(l2.z-l1.z)*(nor.z-l1.z))<0)
return 0;
p1=nor+(l1-l2)/(l1-l2).vl()*sqrt(sqr(r)-sqr(d));
p2=nor+(l2-l1)/(l1-l2).vl()*sqrt(sqr(r)-sqr(d));
return 1;
}
double dist2(point p1,point p2)
{
return sqr(p1.x-p2.x)+sqr(p1.y-p2.y)+sqr(p1.z-p2.z);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
point o,s,t;
scanf("%lf%lf%lf",&o.x,&o.y,&o.z);
double r;
scanf("%lf",&r);
scanf("%lf%lf%lf%lf%lf%lf",&s.x,&s.y,&s.z,&t.x,&t.y,&t.z);
point p1,p2;
int ok=line_ball(s,t,o,r,p1,p2); //判断直线是否和球相交
double os,ot,st;
os=dist(o,s);
ot=dist(o,t);
st=dist(s,t);
double tso = acos((st*st+os*os-ot*ot)/(2*st*os));
double sto = acos((st*st+ot*ot-os*os)/(2*st*ot));
double ans;
if(ok==1&&tso<pi/2&&sto<pi/2) //如果直线st和球相交并且∠tso和∠sto都小于90度,说明线段st和球相交
{
ans=0;
double q1,q2,q3,q4;
q1=acos(r/os);
q2=acos(r/ot);
q3=acos((dist2(o,s)+dist2(o,t)-dist2(s,t))/(2*os*ot));
q4=(q3-q2-q1);
ans+=q4*r;
ans+=sqrt(sqr(o.x-s.x)+sqr(o.y-s.y)+sqr(o.z-s.z)-r*r);
ans+=sqrt(sqr(o.x-t.x)+sqr(o.y-t.y)+sqr(o.z-t.z)-r*r);
printf("%.6f\n",ans);
}
else
{
ans=dist(s,t);
printf("%.6f\n",ans);
}
}
return 0;
}
ACcode:(直线参数方程算d)
#include<bits/stdc++.h>
#define mem(a,b) memset((a),b,sizeof(a))
#define de cout<<endl<<endl<<endl
typedef long long ll;
const int inf=0x3f3f3f3f;
const int N=100010;
const double pi=acos(-1.0);
using namespace std;
struct point{
double x,y,z;
};
double sqr(double x) {return x*x;}
double dist(point p1,point p2)
{
return sqrt(sqr(p1.x-p2.x)+sqr(p1.y-p2.y)+sqr(p1.z-p2.z));
}
double dist2(point p1,point p2)
{
return sqr(p1.x-p2.x)+sqr(p1.y-p2.y)+sqr(p1.z-p2.z);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
point o,s,t;
double r;
scanf("%lf%lf%lf",&o.x,&o.y,&o.z);
scanf("%lf",&r);
scanf("%lf%lf%lf%lf%lf%lf",&s.x,&s.y,&s.z,&t.x,&t.y,&t.z);
double os,ot,st;
os=dist(o,s);
ot=dist(o,t);
st=dist(s,t);
///这部分是用直线的参数方程来算d
double l,m,n;
l=t.x-s.x;
m=t.y-s.y;
n=t.z-s.z;
double tt=(l*o.x-l*s.x+m*o.y-m*s.y+n*o.z-n*s.z)/(m*m+n*n+l*l); //参数
point p; //垂足
p.x=s.x+tt*l;
p.y=s.y+tt*m;
p.z=s.z+tt*n;
double d=dist(p,o);
///这部分是用直线的参数方程来算d
double tso = acos((st*st+os*os-ot*ot)/(2*st*os)); //∠tso
double sto = acos((st*st+ot*ot-os*os)/(2*st*ot)); //∠sto
if(d<r&&tso<pi/2&&sto<pi/2) //如果d小于r并且∠tso和∠sto都小于90度,说明线段st和球相交
{
double q1,q2,q3,q4;
q1=acos(r/os);
q2=acos(r/ot);
q3=acos((dist2(o,s)+dist2(o,t)-dist2(s,t))/(2*os*ot));
q4=q3-q1-q2;
double ans=q4*r;
ans+=sqrt(sqr(o.x-s.x)+sqr(o.y-s.y)+sqr(o.z-s.z)-r*r);
ans+=sqrt(sqr(o.x-t.x)+sqr(o.y-t.y)+sqr(o.z-t.z)-r*r);
printf("%.6f\n",ans);
}
else
{
double ans=dist(s,t);
printf("%.6f\n",ans);
}
}
return 0;
}