Gym - 102056F 2018 EC final F - Interstellar … Fantasy

题目链接

题意:在三维空间里,给你球的圆心坐标,还有半径,再给你两个点 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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值