HDU4793 2013 长沙 C (计算几何—点和圆的位置关系—解法①列方程求解[时间作自变量] —解法②向量积和sin角度求解长度再除以有方向的速度)

0

题目描述:

一个大圆(R),套着一个小圆(Rm),有硬币(r)从大圆外出发(审题未注意到这一条件导致多考虑了两种不必要的情况),假设没有摩擦力,碰到小圆就会碰撞反射出来,未碰到小圆则穿过大圆,或者没有进大圆,问硬币在大圆内待得时间是多少。

1

题目分析:

0]

三种情况,如题目描述中所写:碰到小圆就会碰撞反射出来,未碰到小圆则穿过大圆,或者没有进大圆,尤其注意莫忘最后一种。

首先,一定注意题目已经说明,硬币从大圆外出发,不要忘记速度是矢量,既有大小也有方向,大小也不一定是1。

其次,下面两种解法,都要注意硬币有自己的r,题目要求硬币任何部分在大圆内都属于硬币在大圆内的情况,因此要从硬币紧紧挨着大圆外时,开始考虑,那么下面两种方法,对于大圆R和小圆的半径Rm分别相当于变成了R+r,Rm+r。


1]

解法①列方程求解[时间作自变量],假设(x1,y1)为硬币出发点,(vx,vy)为速度矢量。

x=x1+vx*t;

y=y1+vy*t;

x*x+y*y=(R+r)*(R+r)

x*x+y*y=(Rm+r)*(Rm+r)

联立上式,得t1,t2,t1m,t2m,分别为出大圆、进大圆、出小圆、进小圆的时刻。解法①注意,方程无解的情况,或者t<0的情况,都是说明硬币没有进大圆,输出0。

②  解法②向量积和sin角度求解长度再除以有方向的速度。假设t是时间,s1是未碰撞情况下总轨迹长度,s2是碰撞情况下轨迹长度,v是速度矢量。假设硬币坐标(px,py),速度矢量为(vx,vy)。注意判断方向等使其出现不进入大圆的情况。

t=s/v;

v=sqrt(vx*vx+vy*vy);

s1=sqrt( (R+r)*(R+r) - d*d );

s2=sqrt( (R+r)*(R+r) - dm*dm ) - sqrt( (Rm+r)*(Rm+r) - dm*dm );

d=sin*(R+r); 

dm=sin*(Rm+r);

sin=( (0-px)*(vy) - (0-py)*vx ) / sqrt(px*px + py*py ) / sqrt( vx*vx + vy*vy ):

(另外此处解法②的d的求法中如果仔细探究会发现,sin的求法可能不太好理解,分子使用速度矢量*t来表示那段移动轨迹的矢量,同时分母使用速度矢量的模长(速度的大小)*t来表示那段移动轨迹的矢量大小,最后同时约去了一个t。)


综上,解法②只要列出方程,可以根据两解、一解、无解等情况,来辅助推测题中的坑和陷阱,而解法①需要更清晰的思路,更细致的考虑。

注意学习到,一个点到一条边的距离可以如下计算:

直线L:Ax+By+C=0,直线外点P(Xo,Yo),则,P点到直线L的距离:D=│AXo+BYo+C│/√(A²+B²),

还可用向量叉乘/2得到面积来算,或者向量叉乘得到向量夹角的sin夹角配合斜边来算。

2]

补充关于向量点乘和向量叉乘:

3]


2

解法①

#include <iostream>
#include <math.h>
#include <cmath>

using namespace std;
double Rm,R,r,x,y,vx,vy;
double a,b,c,cm,d,dm;
double t1,t2,t1m,t2m;
int main(){
    while(~scanf("%lf%lf%lf%lf%lf%lf%lf",&Rm,&R,&r,&x,&y,&vx,&vy)){
        a=vx*vx+vy*vy;
        b=2*(x*vx+y*vy);
        c=x*x+y*y-(R+r)*(R+r);
        cm=x*x+y*y-(Rm+r)*(Rm+r);
        d=b*b-4*a*c;
        dm=b*b-4*a*cm;
        if(a==0||d<=0){//a==0和d<0方程无解,d==0则t1==t2即擦过圆而不进圆
            printf("0.000\n");
            continue;
        }
        t1=((-1)*b+sqrt(d))/(2*a);
        t2=((-1)*b-sqrt(d))/(2*a);
        if(t2<0){//t2<t1,而任意一个t小于0,硬币都是往外走而不可能再进大圆
            printf("0.000\n");
            continue;
        }
        if(dm<=0){//dm<=0,硬币穿过大圆而不碰撞小圆
            printf("%.3f\n",t1-t2);
            continue;
        }
        t1m=((-1)*b+sqrt(dm))/(2*a);
        t2m=((-1)*b-sqrt(dm))/(2*a);//如果能走到这一步,t2一定大于0
        printf("%.3f\n",(t1-t1m)+(t2m-t2));

    }
}


解法②

#include <iostream>
#include <cmath>
#include <math.h>

using namespace std;
const double eps=1e-8;//PI=acos(-1,0);
double Rm;double R;double r;double x;double y;double vx;double vy;
struct points{
    double x;
    double y;
}pp,vv;
double t;
double d;
double v;
double len;
int Judge(){//向量点乘(对应x相乘+对应y相乘)(向量内积(向量点乘)和向量外积(向量叉乘),此处是向量点乘,而非向量叉乘)(以(x1,y1)与(x2,y2)为例,向量点乘:x1*x2+y1*y2,向量叉乘:x1*y2-y1*x2=向量(x1,y1)的模 * 向量(x2,y2)的模 * sin(向量夹角))(向量点乘==0,意味着向量夹角为90°,向量点乘小于0,意味着向量夹角大于90°)(向量叉乘=0,意味着sin(向量夹角)==0,则两个向量要么为0°要么为180°),小于0则之间夹角大于90度,等于0则为90度,如题应该严格小于90度才有不为0的答案
    struct points vv2;//(硬币起始点指向起点的矢量)
    vv2.x=(-1)*(pp.x);
    vv2.y=(-1)*(pp.y);
    if((vv2.x*vv.x+vv2.y*vv.y)<0||fabs(vv2.x*vv.x+vv2.y*vv.y)<eps){//注意double变量对于==0的判断,用fabs去正负号以及与eps作比较
        return 1;///千万不要返回-1,因为如果主程序里if(Judge()),那么-1与1的效果是一样的!!!
    }
    else{
        return 0;
    }
}
double get_dis(){
    if(fabs(pp.x*vv.y-vv.x*pp.y)<eps){///不要忘记+fabs去正负号
        return 0;
    }
    double sin=fabs((pp.x)*(vv.y)-(pp.y)*(vv.x))/fabs(v)/fabs(len);//向量外积/向量a的模/向量b的模=sin向量夹角,不需要加fabs,但是为了提醒你这里是模长,不同情况不同应对。
    return sin*len;
}
int main()
{
    while(~scanf("%lf%lf%lf%lf%lf%lf%lf",&Rm,&R,&r,&pp.x,&pp.y,&vv.x,&vv.y)){

        len=sqrt(pp.x*pp.x+pp.y*pp.y);//硬币起始坐标连接原点的直线的绝对值距离
        v=sqrt(vv.y*vv.y+vv.x*vv.x);//硬币的速度矢量(大小和方向)。
        if(Judge()){//方向不对,未曾进大圆
            printf("0.000\n");
            continue;
        }
        d=get_dis();//原点到硬币轨迹的直线距离()
        //printf("%lf\n",d);
        if(d<(R+r)&&d>=(Rm+r)){//从大圆中穿过
            t=sqrt((R+r)*(R+r)-(d*d))/v;
            printf("%.3f\n",t*2);
        }
        else if(d<(Rm+r)){//碰撞(包括撞向圆心的那条轨迹)
            t=( sqrt((R+r)*(R+r)-d*d) - sqrt((Rm+r)*(Rm+r)-d*d) )/v;
            printf("%.3f\n",t*2);
        }else{//方向对了,但是距离过远未曾进大圆
             printf("0.000\n");
        }

    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值