(一眼计算几何)【牛客小白月赛88】 G题 三点不共线

作者前言

这道题的思路其实很清晰,但是实现起来细节问题很多,但是的的确确都是计算几何中需要注意的问题,想要弄懂的话还是建议先去系统地初步学习计算几何的基础知识点,你就会对这个题有更深的理解

题目要求

忘了的话点击此处再看一下

解题思路

我们要根据题目中给的两个点和角度找到另外一个合适的点,那么最直观的就是两个信息:两个点的长度,和第三个点处的角度

我相信聪明的大家理所当然的就能想到一个公式

a 2 = b 2 + c 2 − 2 ∗ b c ∗ c o s ∠ B A C a^2 = b^2 + c^2 - 2*bc*cos\angle BAC a2=b2+c22bccosBAC

如果想到了这个,那么这个题已经写完一半了

我们不难发现,这个题只对角度有要求,在这个广阔的平面直角坐标系中或许有很多点符合这个条件,不过由于它实在是太大了(无穷谁受得了),那么问题就是我们要在一个符合规律集合里寻找才能在有限的时间里找到符合条件的点

再思考一下,可不可能在一条直线上找到呢?

从两点连线的中点处做一条垂直于它的直线,从中点处向一侧延着这条直线看过去,你很轻易发现总有一个点可以连起来后形成的角度符合要求,那么接下来就是二分找到答案就可以了

代码讲解

这么说还是有点抽象

接下来我会逐步讲解我的代码,方便大家理解

首先是准备工作

struct Point
{
    double x,y;
    Point(){}
    Point(double x, double y):x(x),y(y){}
}A,B,C,D;

A点和B点是初始点,C点是AB的中点,D点是在C点处垂直于AB的直线上一点

其次是各类运算

易错点
int sgn(double x, double y)
{
    if(fabs(x-y) < eps) return 0;
    return ((x-y) > 0 ? 1 : -1);
}
// eps = 1e-9

由于这道题有精度问题,所以一定要注意这个计算几何绕不开的事,不是一定要完全一样才是0,只要差在一个范围内都可以是0,不过这个范围不要设置的太小了,像是这个题要求的是1e-6,设置个1e-9就差不了,别大了,精度问题很可怕的,不过此时的作者也是个半吊子,所以具体问题只能尽可能地小一些,1e-10可能就明显的运行时间长一些了,估摸着别太离谱就行了

距离
double dis(Point a,Point b,bool p)
{
    if(p)
        return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    else
        return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}

点和点的距离肯定是需要的啊,但是sqrt以后再平方多此一举还可能有精度问题,所以设置了两个模式,一个是平方,另一个是开方后的

斜率
void slo(Point a,Point b)
{
    if(!sgn(a.x , b.x)) pat = true;
    else{
        pat = false;
        slop = (b.y - a.y)/(b.x - a.x) * -1;
    }
}

要得到垂直后的直线有一个点怎么够,相互垂直的两条直线,斜率互为相反数,不相信可以举几个例子试试

直线方程
double f(double y)
{
    return (slop != 0 ? (y - C.y) / slop : 0) + C.x;
}
// slop是上面那个slo函数算出来的斜率

根据y坐标算出横坐标,你可能会问为什么不用x坐标算出y坐标,都可以,只是写的时候不太一样就是了,其实用x是最合适的,因为我这还要特判一下0的情况,你也可以自己实现一下另一种

核心部分

        if(pat){
        	double l = C.x , r = 1e9;
        	while (sgn(l,r)){
        		double mid = (l + r) / 2;
        		int io = check_x(mid);
        		if(io >= 0){
        			l = mid;
        		}else{
        			r = mid;
        		}
        	}
        	printf("%.10f %.10f\n",l,C.y);
        }else{
        	double l = C.y , r = 1e9;
	        while (sgn(l,r)){
	            double mid = (l + r) / 2;
	            int io = check(mid);
	            if(io >= 0){
	                l = mid;
	            }else{
	                r = mid;
	            }
	        }
	        printf("%.10f %.10f\n",f(l),l);
        }

先展示是为了说明一下本质就是二分

为什么分成了两个呢?

这就不得不说一下我的整个原理了,我发现角度也就只有0~180度,那么只要从AB直线的一侧出发开始找就行了,所以y的范围就是 [ C . y , 1 e 9 ] [C.y , 1e9] [C.y,1e9] ,这样想本身没错,但是有一种情况处理不了,那就是如果AB是一条竖直线呢,跟y没关系了就,需要x的坐标了,所以分成了两种情况

接下来说一下最核心的部分 check函数

int check(double m)
{
    D = Point(f(m) , m);
    double o = acos((dis(D,A,0) + dis(D,B,0) - dis(A,B,0)) / (2 * dis(D,A,1) * dis(D,B,1))) / acos(-1) * 180;
    return sgn(o , angle);
}



int check_x(double m)
{
	D = Point(m , C.y);
    double o = acos((dis(D,A,0) + dis(D,B,0) - dis(A,B,0)) / (2 * dis(D,A,1) * dis(D,B,1))) / acos(-1) * 180;
    return sgn(o , angle);
}

中间的公式很吓人我知道,但是需要你仔细想想

我们是根据 a 2 = b 2 + c 2 − 2 ∗ b c ∗ c o s ∠ B A C a^2 = b^2 + c^2 - 2*bc*cos\angle BAC a2=b2+c22bccosBAC 这个公式得到的,只需要反推过来就行了
∠ B A C = a r c c o s ( b 2 + c 2 − a 2 2 ∗ b c ) \angle BAC = arccos(\frac {b^2 + c^2 - a^2} {2 * bc}) BAC=arccos(2bcb2+c2a2)

好,这样只能得到弧度制,那么我们应该怎么得到角度呢?

a r c c o s ( − 1 ) = π arccos(-1) = \pi arccos(1)=π ,这样你就明白了把,除以这个再乘以180就得到角度了

那么以上一整个就是关键代码的解释,欢迎指正

总代码

#include<bits/stdc++.h>
using namespace std;
const double eps = 1e-9;
struct Point
{
    double x,y;
    Point(){}
    Point(double x, double y):x(x),y(y){}
}A,B,C,D;
int t;
bool pat;
double slop;
double angle;

int sgn(double x, double y)
{
    if(fabs(x-y) < eps) return 0;
    return ((x-y) > 0 ? 1 : -1);
}
void slo(Point a,Point b)
{
    if(!sgn(a.x , b.x)) pat = true;
    else{
        pat = false;
        slop = (b.y - a.y)/(b.x - a.x) * -1;
    }
}
double dis(Point a,Point b,bool p)
{
    if(p)
        return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    else
        return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
double f(double y)
{
    return (slop != 0 ? (y - C.y) / slop : 0) + C.x;
}
int check(double m)
{
    D = Point(f(m) , m);
    double o = acos((dis(D,A,0) + dis(D,B,0) - dis(A,B,0)) / (2 * dis(D,A,1) * dis(D,B,1))) / acos(-1) * 180;
    return sgn(o , angle);
}
int check_x(double m)
{
	D = Point(m , C.y);
    double o = acos((dis(D,A,0) + dis(D,B,0) - dis(A,B,0)) / (2 * dis(D,A,1) * dis(D,B,1))) / acos(-1) * 180;
    return sgn(o , angle);
}

int main()
{
    cin >> t;
    while (t--){
        double x1,y1,x2,y2;
        cin >> x1 >> y1 >> x2 >> y2 >> angle;
        A = Point(x1,y1);
        B = Point(x2,y2);
        C = Point((x1 + x2) / 2 , (y1 + y2) / 2);
        slo(A,B);
        if(pat){
        	double l = C.x , r = 1e9;
        	while (sgn(l,r)){
        		double mid = (l + r) / 2;
        		int io = check_x(mid);
        		if(io >= 0){
        			l = mid;
        		}else{
        			r = mid;
        		}
        	}
        	printf("%.10f %.10f\n",l,C.y);
        }else{
        	double l = C.y , r = 1e9;
	        while (sgn(l,r)){
	            double mid = (l + r) / 2;
	            int io = check(mid);
	            if(io >= 0){
	                l = mid;
	            }else{
	                r = mid;
	            }
	        }
	        printf("%.10f %.10f\n",f(l),l);
        }

    }
}
  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值