前文:在计算几何中,向量是主要工具,而不是解析几何中的数值。
向量和直线的封装模板:
#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;
}
点相关:
点一般在计算几何内与向量等价,于是一般用向量表示一个点,所以在模板中PPP点写作P=OP→P=\overrightarrow{OP}P=OP
角度相关:
向量的旋转:
x⃗⋅y⃗\vec{x}\cdot\vec{y}x⋅y即辐角相加,并且其中一个的原长乘另一个在这里的投影,因此可以要将x⃗\vec{x}x旋转θ\thetaθ,等价于乘以一个模为1的,辐角为θ\thetaθ的向量,可以是(cosθ,sinθ)(cos\theta,sin\theta)(cosθ,sinθ)。
线相关:
直线和线段的向量表示:
表示一条直线只需要一个方向向量和直线上一个点,也就是两个向量;表示一个线段需要所在直线的方向向量与两个线段端点,需要三个线段。表示线段的方法也可以表示直线,于是我们采用三个向量表示一条直线或线段的方法,分别代表单位方向向量,直线/线段上的两个点A,BA,BA,B,并且单位方向向量取AB→\overrightarrow{AB}AB的单位向量。
投影向量:
先求投影长度,再乘投影到的直线的单位方向向量即可。求PPP在直线lll上的投影向量,就是AP→⋅l.base→⋅l.base→\overrightarrow{AP}\cdot\overrightarrow{l.base}\cdot\overrightarrow{l.base}AP⋅l.base⋅l.base
点到直线的垂足坐标:
设现在要求PPP到ABABAB所在直线lll的投影坐标,那么首先取AAA作为起始点,然后AAA到垂足HHH的差向量即是AP→\overrightarrow{AP}AP在lll的投影z→\overrightarrow{z}z,那么P=A+AP→⋅l.base→⋅l.base→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经过,此时大拇指的方向就是叉乘向量的正方向,此时模长为正,反之为负。
由右手定则,我们要判断在直线的哪一侧就只需要判断在方向向量的哪一侧,即AB→×AP→\overrightarrow{AB}\times\overrightarrow{AP}AB×AP的模正负性。
直线和直线的位置关系:
若内积为0,那么垂直;若叉积为0,那么共线(可能平行可能重合);反之是普通的相交。
点是否在线段上:
需要满足两个条件:
(1)共线,叉积为0
(2)PAPAPA与PBPBPB夹角为钝角,点积小于0
线段和线段的位置关系:
如果是垂直或平行,只需要看所在直线是否垂直平行
接下来着重讨论相交这个关系:
判断线段相交,按顺序进行下面两个实验:
(1)快速排斥实验,看以两个线段为对角线且边与坐标轴平行的矩形是否有重合,如果没有,那么一定不重合,否则再进行第二项。判断是否有重合只需判断在两线段在xxx轴的投影是否有重合部分和在yyy轴投影是否有重合部分,如果任意一个没有,那么就一定没有。
(2)跨立实验:分别以两条线段为基准,看另一条线段的两个端点是否在基准线段的两侧,使用叉积判断。
主观上跨立实验足够充要证明是否相交,但不能判断线段共线且无相交的情况,第一个实验可以认为是特判这种情况的。
tips:注意跨立实验,端点可以在另一条线段上,也就是叉积可以为0
点到直线(线段)的距离:
如果是到直线的距离,那么利用等面积法,因为PA→×PB→=2S=∣AB∣⋅h\overrightarrow{PA}\times\overrightarrow{PB}=2S=|AB|\cdot hPA×PB=2S=∣AB∣⋅h,变形即可求得hhh。
如果是到线段的距离,需要判断点在线段所在直线上的投影是否在线段上,如果是,那么到所在直线的距离就是到线段的距离,否则就是点到线段的两个端点的距离的最小值。
线段和线段的距离:
如果两线段相交,距离为0;反之,为每个线段端点到另一条线段的距离的最小值。
直线和直线的交点:
应该建立在不共线的基础之上。
设A,BA,BA,B确定指向l1l1l1,C,DC,DC,D确定直线l2l2l2
不妨设交点为PPP,那么他一定可以由l1l1l1上的一点AAA加上kkk倍的l1l1l1的方向向量l1.basel1.basel1.base得到,不妨设P=A+k⋅l1.base→P=A+k\cdot\overrightarrow{l1.base}P=A+k⋅l1.base,由于相交,那么CP→\overrightarrow{CP}CP和l2.base→\overrightarrow{l2.base}l2.base共线,所以叉积为0,即(P−C)⋅l2.base→=0(P-C)\cdot\overrightarrow{l2.base}=0(P−C)⋅l2.base=0,叉积满足分配律,化简解出kkk即可。
多边形相关:
多边形的存储方法:
按照逆时针或顺时针存储点即可
多边形的面积:
S=∑i=1nOPi→⋅OPi%n+1→2S=\frac{\sum_{i=1}^{n}\overrightarrow{OP_{i}}\cdot\overrightarrow{OP_{i\%n+1}}}{2}S=2∑i=1nOPi⋅OPi%n+1
多边形的凹凸性:
两种定义:
(1)当多边形没有一个内角是优角时,这个多边形是凸多边形,否则是凹多边形。
(2)若把任意一条边延长成直线,如果与其他边没相交,就是凸多边形,反之为凹。
一种充要的判定方法,选择一个特定顺序的三元组{a,b,c}\{a,b,c\}{a,b,c},如果每一个三元组都满足ccc在ab→\overrightarrow{ab}ab的同一方向,那么这个多边形就是凸的,反之为凹。
圆相关:
圆和圆的位置关系:
设两圆心之间距离为disdisdis,两圆半径分别为r1,r2r_{1},r_{2}r1,r2,并且r1>r2r_{1}>r_{2}r1>r2
(1)相离,dis>r1+r2dis>r_{1}+r_{2}dis>r1+r2
(2)相切,dis=r1+r2dis=r_{1}+r_{2}dis=r1+r2
此时开始小圆圆心一定在大圆内
(3)内切,dis=r1−r2dis=r_{1}-r_{2}dis=r1−r2
(4)相交,内切和外切之间即是相交,r1−r2<dis<r1+r2r1-r2<dis<r1+r2r1−r2<dis<r1+r2
(5)否则即是内包含
圆和直线的交点坐标:
求出到弦的垂足hhh和一半的弦长,向量加减即可
圆和圆的交点坐标:
由两个圆心和一个交点组成的三角形都是已知的,求出一个以圆心为顶点的角,之后将圆心向量旋转即可。
过点PPP的切线与圆的切点:
切点,PPP,OOO三点的直角三角形可以得到角度,旋转即可。
圆的公切线的切点:
找角度,旋转圆心向量即可,注意切线种类有两种,不经过两圆之间和经过两圆之间。
圆和多边形的面积并
圆和圆的面积并/交:
讨论圆和圆的位置关系即可
圆和多边形的面积交:
利用计算多边形面积的”有向面积法“,讨论每个边的情况:
(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;//线段在外部
}