2018-2019 ACM-ICPC, Asia East Continent Finals F - Interstellar … Fantasy (几何)

本文介绍了一种算法,用于计算给定球心坐标、半径和两点A、B,如何找到从A到B的最短路径,路径需沿球面绕过而不穿越球体。通过判断线段与球的关系及使用圆心角和余弦定理,解决了两点间直线距离和经过球面切线的问题。
摘要由CSDN通过智能技术生成

题意:
给出你一个球的球心坐标和球的半径,然后给出你起点和终点的坐标,问你想要从起点走到终点的最短路径是多长,路径不可以穿过球体,需要从表面绕过。
思路:
很明显分为两种情况
1. 1. 1.最短距离就是两点之间的连线。
2. 2. 2.最短距离需要经过圆的表面。

先考虑,最短距离就是两点连线的情况。设圆心 O O O线段 s t st st的距离为 d d d,注意这里是到线段的距离,假如 d ≥ r d \geq r dr,那么两点就可以连线且不经过圆直接到达。

再考虑经过圆的表面,通过 s , t s,t s,t两点做球的切线,设两个切点为 O 1 , O 2 O_1,O_2 O1,O2,那么最短距离就是 s − > O 1 + t − > O 2 + s->O_1 + t->O_2 + s>O1+t>O2+ O 1 , O 2 O_1,O_2 O1,O2的长度。
而弧的长度我们考虑可以通过对应的圆心角求出,而圆心角用余弦定理求出,这样就都解决。

点到线段距离套用的蓝书的板子。

#include <bits/stdc++.h>

using namespace std;

#define pb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const db PI = acos(-1.0);

int dcmp(double x) {//判符号
	if(fabs(x) <= eps) return 0;
	if(x > 0) return 1;
	else return -1;
}

struct Point
{
	double x,y,z;
	Point(){}
	//定义运算 
	Point(double _x,double _y,double _z){x = _x;y = _y;z = _z;}
	Point operator + (const Point &b)const{
		return Point(x+b.x,y+b.y,z+b.z);
	}
	Point operator - (const Point &b)const{
		return Point(x-b.x,y-b.y,z-b.z);
	}
};


typedef Point Vector;

double pf(double x) { return x * x; }
double dis(Point a,Point b) {
	return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z));
}
double dot(Vector a,Vector b) {
	return a.x * b.x + a.y * b.y + a.z * b.z; 
}
double Length(Vector a) {
	return sqrt(dot(a,a));
}
Vector cross(Vector a,Vector b) {//注意点积是一个向量
	return Vector(a.y * b.z - b.y * a.z,a.z * b.x - b.z * a.x,a.x * b.y - b.x * a.y);
}
bool equal(Point a,Point b) {
	return (a.x == b.x && a.y == b.y && a.z == b.z);
}

double distosegment(Point p,Point a,Point b) {//点到线段的距离
	if(equal(a,b)) return Length(p-a);
	Vector v1 = b - a,v2 = p - a,v3 = p - b;
	if(dcmp(dot(v1,v2)) < 0) return Length(v2);
	else if(dcmp(dot(v1,v3)) > 0) return Length(v3);
	else return Length(cross(v1,v2)) / Length(v1);
}

int main() {
	double r,ans;
	Point o,s,t;
	int T;scanf("%d",&T);
	while(T--) {
		scanf("%lf%lf%lf%lf",&o.x,&o.y,&o.z,&r);
		scanf("%lf%lf%lf%lf%lf%lf",&s.x,&s.y,&s.z,&t.x,&t.y,&t.z);
		Vector st(t-s),so(o-s);
		double dst = dis(s,t);
		double dost = distosegment(o,s,t);
		if(equal(s,t)) {
			ans = 0;
			printf("%.8f\n",ans);
			continue;
		}
		if(fabs(dost - r) < eps || dost - r > eps) {//线段不经过球 注意等于的判断情况
			ans = dst;
			printf("%.8f\n",dst);
			continue;
		}
		double dso = dis(o,s),dto = dis(o,t);
		double t1 = acos((pf(dst) + pf(dso) - pf(dto)) / (2.0 * dso * dst));
		double t2 = acos((pf(dto) + pf(dst) - pf(dso)) / (2.0 * dst * dto));
		double angle1 = acos((pf(dso) + pf(dto) - pf(dst)) / (2.0 * dso * dto));
		double angle2 = acos(r/dso),angle3 = acos(r/dto);
		double alpha = angle1 - angle2 - angle3;
		double dd = r * alpha;
		ans = dso * sin(angle2) + dto * sin(angle3) + dd;
		printf("%.8f\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值