前文:在计算几何中,向量是主要工具,而不是解析几何中的数值。
向量和直线的封装模板:
#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=∣AB∣⋅h,变形即可求得 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+k⋅l1.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 (P−C)⋅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=2∑i=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=r1−r2
(4)相交,内切和外切之间即是相交, r 1 − r 2 < d i s < r 1 + r 2 r1-r2<dis<r1+r2 r1−r2<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;//线段在外部
}