计算几何模板

前文:在计算几何中,向量是主要工具,而不是解析几何中的数值。

向量和直线的封装模板:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const double eps=1e-8,pi=acos(-1);

int cmp(double a,double b)
{
    if(fabs(a-b)<eps) return 0;
    else if(a-b>eps) return 1;
    return -1;
}

struct vec
{
    double x,y;
    bool operator==(const vec& a) const {return fabs(x-a.x)<eps&&fabs(y-a.y)<eps;}
    vec operator+(const vec& a) const {return (vec){x+a.x,y+a.y};}
    vec operator-(const vec& a) const {return (vec){x-a.x,y-a.y};}
    vec operator*(const double& k) const {return (vec){x*k,y*k};}
    vec operator/(const double& k) const {return (vec){x/k,y/k};}
    double operator^(const vec& a) const {return x*a.y-y*a.x;}//获得叉积的模
    double operator*(const vec& a) const {return x*a.x+y*a.y;}
    friend istream& operator>>(istream& o,const vec& a)
    {
        scanf("%lf%lf",&a.x,&a.y);
        return o;
    }
    friend ostream& operator<<(ostream& o,const vec& a)
    {
        printf("%.10lf %.10lf ",a.x,a.y);
        return o;
    }
};

struct line
{
    vec base,a,b;//两条向量和方向向量
};

bool rui(const vec& a,const vec& b){return a*b>eps;}//判定是否是锐角
bool zhi(const vec& a,const vec& b){return fabs(a*b)<eps;}//是否直角
bool dun(const vec& a,const vec& b){return a*b<-eps;}//是否钝角
bool operator!=(const vec& a,const vec& b){return !(a==b);}
double S(const vec& a,const vec& b){return fabs(a^b)/2;}//面积,叉积的模/2
vec operator*(const double& k,const vec &a){return (vec){k*a.x,k*a.y};}
double getlen(const vec& a){return sqrt(a.x*a.x+a.y*a.y);}//模长
double getangle(const vec& a,const vec& b){return acos(a*b/(getlen(a)*getlen(b)));}//获得夹角
vec getvertical(const vec& a){return (vec){a.y,-a.x};}//外积求垂直向量
vec getsingle(const vec& a){return a/getlen(a);}//单位向量
vec rotate(const vec& a,double k){return (vec){a.x*cos(k)-a.y*sin(k),a.x*sin(k)+a.y*cos(k)};}//旋转k弧度
line trans(const vec& a,const vec& b){return (line){getsingle(b-a),a,b};}//两点确定一条直线
line trans(double a,double b,double x,double y){return (line){getsingle((vec){x-a,y-b}),(vec){a,b},(vec){x,y}};}//两点确定一条直线

struct circle
{
    vec o;
    double r;
    friend istream& operator>>(istream& o,const circle& O)
    {
        cin>>O.o; scanf("%lf",&O.r);
        return o;
    }
};

int main()
{

    return 0;
}

点相关:

点一般在计算几何内与向量等价,于是一般用向量表示一个点,所以在模板中 P P P点写作 P = O P → P=\overrightarrow{OP} P=OP

角度相关:

向量的旋转:

x ⃗ ⋅ y ⃗ \vec{x}\cdot\vec{y} x y 即辐角相加,并且其中一个的原长乘另一个在这里的投影,因此可以要将 x ⃗ \vec{x} x 旋转 θ \theta θ,等价于乘以一个模为1的,辐角为 θ \theta θ的向量,可以是 ( c o s θ , s i n θ ) (cos\theta,sin\theta) (cosθ,sinθ)

线相关:

直线和线段的向量表示:

表示一条直线只需要一个方向向量和直线上一个点,也就是两个向量;表示一个线段需要所在直线的方向向量与两个线段端点,需要三个线段。表示线段的方法也可以表示直线,于是我们采用三个向量表示一条直线或线段的方法,分别代表单位方向向量,直线/线段上的两个点 A , B A,B A,B,并且单位方向向量取 A B → \overrightarrow{AB} AB 的单位向量。

投影向量:

先求投影长度,再乘投影到的直线的单位方向向量即可。求 P P P在直线 l l l上的投影向量,就是 A P → ⋅ l . b a s e → ⋅ l . b a s e → \overrightarrow{AP}\cdot\overrightarrow{l.base}\cdot\overrightarrow{l.base} AP l.base l.base

点到直线的垂足坐标:

设现在要求 P P P A B AB AB所在直线 l l l的投影坐标,那么首先取 A A A作为起始点,然后 A A A到垂足 H H H的差向量即是 A P → \overrightarrow{AP} AP l l l的投影 z → \overrightarrow{z} z ,那么 P = A + A P → ⋅ l . b a s e → ⋅ l . b a s e → P=A+\overrightarrow{AP}\cdot\overrightarrow{l.base}\cdot\overrightarrow{l.base} P=A+AP l.base l.base

角平分线:

菱形形对角线即是角平分线,那么 a → + b → \overrightarrow{a}+\overrightarrow{b} a +b 就是角平分线向量,直线取单位向量即可。

tips:注意是等长度向量相加
中垂线:

先找垂线的方向向量,利用点积为0;然后中点坐标,求出中点即可。

点关于直线的对称点:

求出点在直线的投影垂足后,用中点坐标公式即可

点与直线的位置关系:

点在直线的哪一侧可以用叉积解决。

右手定则: a → × b → \overrightarrow{a} \times\overrightarrow{b} a ×b ,右手指弯曲,先从 a → \overrightarrow{a} a 经过,此时大拇指的方向就是叉乘向量的正方向,此时模长为正,反之为负。

由右手定则,我们要判断在直线的哪一侧就只需要判断在方向向量的哪一侧,即 A B → × A P → \overrightarrow{AB}\times\overrightarrow{AP} AB ×AP 的模正负性。

直线和直线的位置关系:

若内积为0,那么垂直;若叉积为0,那么共线(可能平行可能重合);反之是普通的相交。

点是否在线段上:

需要满足两个条件:

(1)共线,叉积为0

(2) P A PA PA P B PB PB夹角为钝角,点积小于0

线段和线段的位置关系:

如果是垂直或平行,只需要看所在直线是否垂直平行

接下来着重讨论相交这个关系:

判断线段相交,按顺序进行下面两个实验:

(1)快速排斥实验,看以两个线段为对角线且边与坐标轴平行的矩形是否有重合,如果没有,那么一定不重合,否则再进行第二项。判断是否有重合只需判断在两线段在 x x x轴的投影是否有重合部分和在 y y y轴投影是否有重合部分,如果任意一个没有,那么就一定没有。

(2)跨立实验:分别以两条线段为基准,看另一条线段的两个端点是否在基准线段的两侧,使用叉积判断。

主观上跨立实验足够充要证明是否相交,但不能判断线段共线且无相交的情况,第一个实验可以认为是特判这种情况的。

tips:注意跨立实验,端点可以在另一条线段上,也就是叉积可以为0
点到直线(线段)的距离:

如果是到直线的距离,那么利用等面积法,因为 P A → × P B → = 2 S = ∣ A B ∣ ⋅ h \overrightarrow{PA}\times\overrightarrow{PB}=2S=|AB|\cdot h PA ×PB =2S=ABh,变形即可求得 h h h

如果是到线段的距离,需要判断点在线段所在直线上的投影是否在线段上,如果是,那么到所在直线的距离就是到线段的距离,否则就是点到线段的两个端点的距离的最小值。

线段和线段的距离:

如果两线段相交,距离为0;反之,为每个线段端点到另一条线段的距离的最小值。

直线和直线的交点:

应该建立在不共线的基础之上。

A , B A,B A,B确定指向 l 1 l1 l1 C , D C,D C,D确定直线 l 2 l2 l2

不妨设交点为 P P P,那么他一定可以由 l 1 l1 l1上的一点 A A A加上 k k k倍的 l 1 l1 l1的方向向量 l 1. b a s e l1.base l1.base得到,不妨设 P = A + k ⋅ l 1. b a s e → P=A+k\cdot\overrightarrow{l1.base} P=A+kl1.base ,由于相交,那么 C P → \overrightarrow{CP} CP l 2. b a s e → \overrightarrow{l2.base} l2.base 共线,所以叉积为0,即 ( P − C ) ⋅ l 2. b a s e → = 0 (P-C)\cdot\overrightarrow{l2.base}=0 (PC)l2.base =0,叉积满足分配律,化简解出 k k k即可。

多边形相关:

多边形的存储方法:

按照逆时针或顺时针存储点即可

多边形的面积:

S = ∑ i = 1 n O P i → ⋅ O P i % n + 1 → 2 S=\frac{\sum_{i=1}^{n}\overrightarrow{OP_{i}}\cdot\overrightarrow{OP_{i\%n+1}}}{2} S=2i=1nOPi OPi%n+1

多边形的凹凸性:
两种定义:

(1)当多边形没有一个内角是优角时,这个多边形是凸多边形,否则是凹多边形。

(2)若把任意一条边延长成直线,如果与其他边没相交,就是凸多边形,反之为凹。

一种充要的判定方法,选择一个特定顺序的三元组 { a , b , c } \{a,b,c\} {a,b,c},如果每一个三元组都满足 c c c a b → \overrightarrow{ab} ab 的同一方向,那么这个多边形就是凸的,反之为凹。

圆相关:

圆和圆的位置关系:

设两圆心之间距离为 d i s dis dis,两圆半径分别为 r 1 , r 2 r_{1},r_{2} r1,r2,并且 r 1 > r 2 r_{1}>r_{2} r1>r2

(1)相离, d i s > r 1 + r 2 dis>r_{1}+r_{2} dis>r1+r2

(2)相切, d i s = r 1 + r 2 dis=r_{1}+r_{2} dis=r1+r2

此时开始小圆圆心一定在大圆内

(3)内切, d i s = r 1 − r 2 dis=r_{1}-r_{2} dis=r1r2

(4)相交,内切和外切之间即是相交, r 1 − r 2 < d i s < r 1 + r 2 r1-r2<dis<r1+r2 r1r2<dis<r1+r2

(5)否则即是内包含

圆和直线的交点坐标:

求出到弦的垂足 h h h和一半的弦长,向量加减即可

圆和圆的交点坐标:

由两个圆心和一个交点组成的三角形都是已知的,求出一个以圆心为顶点的角,之后将圆心向量旋转即可。

过点 P P P的切线与圆的切点:

切点, P P P O O O三点的直角三角形可以得到角度,旋转即可。

圆的公切线的切点:

找角度,旋转圆心向量即可,注意切线种类有两种,不经过两圆之间和经过两圆之间。

圆和多边形的面积并

圆和圆的面积并/交:

讨论圆和圆的位置关系即可

圆和多边形的面积交:

利用计算多边形面积的”有向面积法“,讨论每个边的情况:

(0)线段经过原点

(1)与圆有一个交点

(2)与圆有两个交点

(3)线段完全在圆内

(4)线段完全在圆外

判断情况是比较繁琐的,可以先判断简单的,然后在else区域内再进一步判断,会显得容易一些。

对于情况(1)(2),用余弦定理是非常麻烦的,只要直线交圆,就一定有垂径定理的三角形,那么就有角度,利用角度运算,会简单许多。

情况(1)(2)中 θ \theta θ为弦与半径的夹角,然后运算即可。

int gettot(vec p1,vec p2,double len,double dis,double len1,double len2,double len3)
{
	//两个交点
	if(cmp(len,dis)==0&&cmp(len1,s.r)>-1&&cmp(len2,s.r)>-1&&cmp(len,s.r)==-1) return 2;
	if(cmp(len2,s.r)<1&&cmp(len1,s.r)>-1) return 1;
	return 0;
}

double solve(vec p1,vec p2)
{
	if(on_seg(trans(p1,p2))) return 0;
	double len1=getlen(p1),len2=getlen(p2),len3=getlen(p2-p1);
	if(cmp(len1,len2)==-1) swap(len1,len2);
	double dis=fabs(p1^p2)/len3,len=getdis({0,0},trans(p1,p2));
	int tot=gettot(p1,p2,len,dis,len1,len2,len3),base=(p1^p2)>0?1:-1;
	if(tot==1)
	{
		double theta=asin(dis/s.r),alpha=acos(getcos(len2,len3,len1)),beta=acos(getcos(len1,len2,len3));
		return base*((beta+alpha+theta-pi)*s.r*s.r/2+sin(pi-theta-alpha)*s.r*len2/2);
	}
	else if(tot==2)
	{
		double theta=asin(dis/s.r),alpha=asin(dis/len1),beta=asin(dis/len2);
		double ret1=(theta-alpha)*s.r*s.r/2,ret2=(theta-beta)*s.r*s.r/2,ret3=s.r*s.r*sin(pi-2*theta)/2;
		return base*(ret1+ret2+ret3);
	}
	if(cmp(len1,s.r)==-1&&cmp(len2,s.r)==-1) return base*S(p1,p2);//线段完全在内部
	return base*acos(getcos(len1,len2,len3))*s.r*s.r/2;//线段在外部
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值