SGU110 Dungeon
题目大意
空间探测器在星球M上发现了巨大的地牢,地牢被明亮的球充满,探测器发现光线能按自然规律被球表面反射(入射角等于反射角,入射光线、反射光线、法线在同一平面)。古老的传说说如果光按一定顺序被球表面反射,房间的门就会打开。
你不需要去猜这个顺序;你的任务更简单一些。你会知道球的位置和半径、激光发射的位置及光传播的方向。你要找出光被球反射的顺序。
输入
n(1<=n<=50)球的数量 下面n行读入球坐标,半径xi, yi, zi, ri(integer范围内)。
最后一行包含6个数。
前三个是激光发射的坐标(发射点严格的在任何球体外)。
后三个告诉你激光发射的方向(这个坐标在光线上)。
输出
光被球反射的顺序(球在输入中从1开始依次编号)。
如果球被反射10次以上,输出前十次,然后用一个空格和'etc.'隔开(不含引号)。
注意:如果光的轨迹是某个球的切线,认为光被球反射。
样例输入1
1
0 0 2 1
0 0 0 0 0 1
样例输出1
1
样例输入2
2
0 0 2 1
0 0 -2 1
0 0 0 0 0 100
样例输出2
1 2 1 2 1 2 1 2 1 2 etc.
第一道吓到我的题目。
首先想到的是解析式求解交点,不过扩展到三维的时候成功挂掉。。。
更改思路使用向量,就可以成功AC。
对于点(xs,ys,zs)和光线向量(xl,yl,zl),在这条光线上的点(x,y,z)满足:
x=xs+k*xl;
y=ys+k*yl;
z=zs+k*zl;
其中,k∈R
对于球(x0,y0,z0)和半径r,在这个球上的点(x,y,z)满足:
(x0-x)^2+(y0-y)^2+(z0-z)^2=r^2;
联立上述四个式子,可以得到一个关于k的一元二次方程。
若该方程无解,则证明两者不相交。
在得到交点之后,问题就是反射了(请先行了解向量加减法的几何意义和光的反射定律)
法线向量v1为从球心指向交点的向量。
入射的反向量v2为从交点指向出发点的向量。
我们可以利用公式求出v1在v2上的投影向量v3(投影公式参考网友博客:向量投影)。
然后将(v3-v1)*2+v1得到反射向量(无法理解的话可以在二维平面上模拟画图帮助理解)。
反射出发点变为交点。
注意事项:1.一元二次方程的解可能有两个,我们要取的是离出发点进的那个交点。
2.光线是一条射线而不是直线,所以一元二次方程的解应该>0。
3.在变换向量时入射向量不等于光线向量(长度不同),不能混用。
4.开始时记得计算光线向量(输入所给并非光线向量,而是这上面的一个点)。
下面附上我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
struct b
{
double x,y,z;
double r;
bool pass;
}ball[51],now,line,next,zero;
int n;
double dis(struct b a1,struct b a2)
{
return (sqrt((a1.x-a2.x)*(a1.x-a2.x)+(a1.y-a2.y)*(a1.y-a2.y)+(a1.z-a2.z)*(a1.z-a2.z)));
}
struct b equation(double a,double b,double c) //解方程ak^2+bk+c=0和对应的交点
{
double k1,k2;
struct b x1,x2;
x1.pass=false;
k1=(-b+sqrt(b*b-4*a*c))/(2*a);
x1.x=k1*line.x+now.x;
x1.y=k1*line.y+now.y;
x1.z=k1*line.z+now.z;
k2=(-b-sqrt(b*b-4*a*c))/(2*a);
x2.x=k2*line.x+now.x;
x2.y=k2*line.y+now.y;
x2.z=k2*line.z+now.z;
if ((k1==k2 || dis(now,x1)<dis(now,x2)) && k1>0)
{
x1.pass=true;
return x1;
}
else if (k2>0)
{
x2.pass=true;
return x2;
}
return x1;
}
struct b find(int k) //判断交点
{
double a,b,c;
struct b re;
if (k==2)
k=2;
re.pass=false;
a=line.x*line.x+line.y*line.y+line.z*line.z;
b=2*line.x*(now.x-ball[k].x)+2*line.y*(now.y-ball[k].y)+2*line.z*(now.z-ball[k].z);
c=(now.x-ball[k].x)*(now.x-ball[k].x)+(now.y-ball[k].y)*(now.y-ball[k].y)+(now.z-ball[k].z)*(now.z-ball[k].z)-ball[k].r*ball[k].r;
if (a!=0 && b*b>=4*a*c)
re=equation(a,b,c);
else if (a==0 && b!=0 && (-c)/b>0)
{
re.x=(-c)/b*line.x+now.x;
re.y=(-c)/b*line.y+now.y;
re.z=(-c)/b*line.z+now.z;
re.pass=true;
}
return re;
}
double point(struct b a1,struct b a2)
{
return a1.x*a2.x+a1.y*a2.y+a1.z*a2.z;
}
struct b makev(struct b a1,struct b a2)
{
struct b re;
re.x=a2.x-a1.x;
re.y=a2.y-a1.y;
re.z=a2.z-a1.z;
return re;
}
struct b change(struct b a1,double i)
{
struct b re;
re.x=a1.x*i;
re.y=a1.y*i;
re.z=a1.z*i;
return re;
}
struct b add(struct b a1,struct b a2)
{
struct b re;
re.x=a1.x+a2.x;
re.y=a1.y+a2.y;
re.z=a1.z+a2.z;
return re;
}
void turn(int k)
{
struct b v1,v2,v3,v4,v5;
double i;
v1=makev(next,now); //从交点向起始点指一个向量
v2=makev(ball[k],next); //从球心向交点指一个向量
i=point(v1,v2)/(dis(zero,v2)*dis(zero,v2));
v3=change(v2,i); //以上两步求出投影向量v3
v4=makev(v1,v3); //从v1向v3指一个向量(实际就是减法)
v5=change(v4,2); //将上面的向量延伸
line=add(v1,v5); //计算反射向量
return ;
}
void work()
{
int t,i,ballk,lastball=0;
struct b k;
t=1;
while (t<=11)
{
next.pass=false;
for (i=1;i<=n;i++)
if (i!=lastball) //因为起点在球上,特判
{
k=find(i); //对第i个球进行判断
if (k.pass && (!next.pass || dis(k,now)<dis(next,now))) //判断交点更新
{
next=k;
next.pass=true;
ballk=i;
}
}
if (!next.pass)
break;
turn(ballk); //光线反射
now=next; //更改入射点
if (t<=10)
if (t==1)
printf("%d",ballk);
else
printf(" %d",ballk);
else
printf(" etc.");
t++;
lastball=ballk; //记录上一个到达的球
}
printf("\n");
return ;
}
void init()
{
int i;
scanf("%d",&n);
for (i=1;i<=n;i++)
scanf("%lf%lf%lf%lf",&ball[i].x,&ball[i].y,&ball[i].z,&ball[i].r);
scanf("%lf%lf%lf%lf%lf%lf",&now.x,&now.y,&now.z,&line.x,&line.y,&line.z);
line.x-=now.x;
line.y-=now.y;
line.z-=now.z; //计算向量
work();
return ;
}
int main()
{
init();
return 0;
}