就是现在空间中给定球,平面和点的位置, 从点的位置作为点光源散发出光求球在平面上的投影面积。
本题并没有用到复杂的算法, 但是对于一些数学上的几何知识提出了一定的要求, 投影如果存在的话不是圆就是椭圆(可以证明), 最复杂的情况下利用几何关系求出椭圆的半长轴, 半焦距和半短轴中的任意两个即可, 椭圆面积公式高数上应该就已经讲过可以用一维的换元积分求得了。
由于题意只保证输入的点, 球, 平面两两不相交, 所以不同的位置关系需要分为以下几种情况
显然在情形一下投影面积为零, 此时球心和点光源L在平面的异侧, 很容易判断
情形二下投影面积也是零, 用 dl 表示点光源到平面P的距离, dr 表示球心到平面P的距离, r表示球O的半径显然这种情形发生的条件是
dl≤dr−r 且球心与点光源L在平面的同一侧
情形三下投影面积是无穷大, 用 dl 表示点光源到平面 P 的距离, dr 表示球心到平面P的距离, r 表示球 O 的半径显然这种情形发生的条件是
dr−r<dl≤dr+r 且球心与点光源L在平面的同一侧
当以上三种情形都不满足时, 投影面积才需要一定的公式计算
计算投影面积时, 注意到投影可能有两种情况
情形四下点光源和球心的连线和平面P垂直, 此时投影是一个圆, 很容易计算面积
情形五下光源和球心的连线与平面P不垂直, 此时投影是一个椭圆, 也就是本题最难的部分, 需要利用几何关系来确定椭圆的样子
接下来对于每种情况给出数学上的判断条件和计算方式
初始化输入得到的条件:
点光源坐标 L(xl,yl,zl)
球心坐标 O(xr,yr,zr)
球的半径 r
平面 P 的方程为 ax+by+cz+d=0
设光源到平面的距离为 dl , 球心到平面的距离为 dr
利用点到平面的距离公式可知
dl=∣axl+byl+czl+d∣a2+b2+c2√ dr=∣axr+byr+czr+d∣a2+b2+c2√
先判断情形一:
根据空间中两个点是否在平面同一侧的判定方法
如果 axl+byl+czl+d 和 axr+byr+czr+d 正负性相同则说明两个点在同一侧, 相反则在异侧 (本题输入保证了这两个不会是0)
判断情形二:
按照点到平面的距离公式计算出了 dl 和 dr 之后判断 dl≤dr−r 是否成立即可
判断情形三:
dr−r<dl≤dr+r 是否成立即可
当以上三种都不满足时才考虑情形四和情形五
考虑向量 LO−→− 和平面的法向量 n→ 平行
其中 LO−→−={xr−xl,yr−yl,zr−zl},n→={a,b,c}
判断两个向量是否平行最简单的写法当然是叉积为0了(也可以用别的方法来判, 这里为了方便起见我直接用叉积了)…用 i→j→k→ 分别表示坐标轴三个方向的单位向量
LO−→− // n→ ⇔ LO−→−×n→=0→ 所以有
LO−→−×n→=
这一情形是所有情形中最复杂的, 首先可以发现以点 L 作为顶点, 和球 O 相切的所有光线形状其实就是一个圆锥的形状, 而一个这样的一个高为无穷的圆锥用一个平面斜着切一下, 切面一定是椭圆, 如图所示, 以 L,S,T 所在平面的截图如图所示, 在这样一个圆锥用平面横切的图中可以放入两个球(如图中虚线所示)对于切面边缘的点 A,B , 利用圆的切线性质 AF=AM′,AF′=AJ,BK=BF′,BN′=BF 而 M′J=N′K 这正是因为F和F’是椭圆的两个焦点的缘故, 具体圆锥上切一刀得到圆锥曲线的证明可以参见
Dandelin双球证明法
wiki: Dandelin Spheres
百度百科:Dandelin
知乎:怎么证明圆锥的截痕是椭圆、双曲线?
当前的已知条件:
LS=dl,OT=dr,OM=ON=r,
LO=(xl−xr)2+(yl−yr)2+(zl−zr)2−−−−−−−−−−−−−−−−−−−−−−−−−−−√
记 d=LO , 设圆 O′ 的半径是 R , 接下来的 d 表示的都是 LO
由 △LSI∼△OTI 可得
LSOT=LIOI 而 LIOI=LO+OIOI=LOOI+1
解得 OI=drddl−dr 则 LI=LO+OI=dlddl−dr
由 △LON∼△LO′N′ 有 LOLO′=ONO′N′=rR
所以 LOLO+OO′=rR 得到 OO′=(R−r)dr
那么 O′I=OI−OO′=drdl−drd−R−rrd
由 △IO′F∼△IOT 有 IO′IO=O′FOT
即 drdl−drd−R−rrddrdl−drd=Rdr , 解得 R=rdlr+dl−dr
那么由勾股定理 LN′=O′L2−R2−−−−−−−−−√=Rd2−r2√r
假设椭圆的三个参数分别为 ae,be,ce (半长轴, 半短轴, 半焦距)
那么按照椭圆的性质 AF=ae−ce,AB=2ae
由圆的切线性质 AM′=AF,BN′=BF , 所以 △LAB 的周长
C△LAB=4ae+2L′N
△LAB 的面积 S△LAB=C△LAB×R2=2aeR+R2d2−r2√r
而从另外一个角度 S△LAB=LS×BS2=dlae
联立两个面积表达式可得 ae=R2d2−r2√(dl−2R)r
接下来为了计算方便,使用当一个平面截圆锥得到圆锥曲线时的结论:
椭圆的离心率等于截面和圆锥的轴的交角的余弦 cosβ 与圆锥的母线和轴所成的角的余弦 cosα 之比
在本题中 β=∠LIS,α=∠MLO
cosα=LMLO=d2−r2√d
cosβ=ISIL=IL2−LS2√IL=1−(dl−dr)2d2−−−−−−−−−√
那么椭圆离心率 e=cosβcosα=d2−(dl−dr)2d2−r2−−−−−−−−√
由于 e=ceae
那么椭圆的面积 Seclipse_shadow_area=πaebe=πa2e(1−e2)−−−−−−−√
带入 ae 和 e 之后最终得到椭圆面积的表达式如下, 化简过程较长这里就略去了…当然写代码的话完全没有必要化简, 毕竟 e,ae 都已经临时存储了
Seclipse_shadow_area=πr2d2l(dl−dr)2−r2d2−r2(dl−dr)2−r2−−−−−−−−√
不难发现和情形四相比椭圆的面积就是相同距离的 dl 和 dr 下圆的面积比上椭圆的离心率, 毕竟数学之美(>_<), 其实短半轴的长度就是 dl 和 dr 相同下的情形四的圆的半径(大家可以自己证明, 利用这个可以不用到那个离心率的结论就将此题解出)
/*
* Author: Gatevin
* Created Time: 2015/3/7 16:09:35
* File Name: NoGameNoLife.cpp
*/
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
int Cases, A, B, C, D, xl, yl, zl, xr, yr, zr, r;
double dl, dr, d;
const double PI = acos(-1.0);
int sgn(double del)
{
return del < -eps ? -1 : (del > eps ? 1 : 0);
}
int main()
{
//freopen("NoGameNoLife.in", "r", stdin);
//freopen("NoGameNoLife.out", "w", stdout);
scanf("%d", &Cases);
while(Cases--)
{
scanf("%d %d %d %d", &A, &B, &C, &D);
scanf("%d %d %d", &xl, &yl, &zl);
scanf("%d %d %d %d", &xr, &yr, &zr, &r);
//情形一
int sgnl = A*xl + B*yl + C*zl + D > 0 ? 1 : -1;
int sgnr = A*xr + B*yr + C*zr + D > 0 ? 1 : -1;
if(sgnl*sgnr == -1)
{
printf("0.00000\n");
continue;
}
//情形二
dl = abs(A*xl + B*yl + C*zl + D)*1./sqrt(A*A*1. + B*B + C*C);
dr = abs(A*xr + B*yr + C*zr + D)*1./sqrt(A*A*1. + B*B + C*C);
if(sgn(dr - r - dl) != -1)
{
printf("0.00000\n");
continue;
}
//情形三
if(dr - r < dl && sgn(dl - dr - r) != 1)
{
printf("Kuhaku is undefeated\n");
continue;
}
//情形四
if((yr - yl)*C == (zr - zl)*B && (xr - xl)*C == (zr - zl)*A && (xr - xl)*B == (yr - yl)*A)
{
double S = PI*dl*1.*dl*r*r*1./((dl - dr)*(dl - dr)*1. - r*r*1.);
printf("%.5f\n", S);
continue;
}
//情形五
d = sqrt((xr - xl)*(xr - xl)*1. + (yr - yl)*(yr - yl)*1. + (zr - zl)*(zr - zl)*1.);
double S = PI*dl*1.*dl*r*r*1./((dl - dr)*(dl - dr)*1. - r*r*1.);
S *= sqrt((d*d*1. - r*r)/((dl*1. - dr)*(dl*1. - dr) - r*r*1.));
printf("%.5f\n", S);
}
return 0;
}