OI计算几何 简单学习笔记
学习平面几何,首先我们要会熟练地应用向量,其次也要知道一些基本的几何知识。(其实看看数学课本就可以了吧)
因为是看的蓝书,所以很多东西做了引用。(update:还参考了赵和旭dalao的讲义)
下面先介绍一些常用的操作:
点的定义
struct point
{
double x,y;
point(double x=0,double y=0):x(x),y(y){}
};
向量的定义
typedef point vec
值的判断
inline int dcmp(double x)
{
if(fabs(x)<eps) return 0;
else if(x<0.0) return -1;
else return 1;
}
向量的基本运算
vec operator + (vec a,vec b){return vec(a.x+b.x,a.y+b.y);}
vec operator - (vec a,vec b){return vec(a.x-b.x,a.y-b.y);}
vec operator * (vec a,double b){return vec(a.x*b,a.y*b);}
vec operator / (vec a,double b){return vec(a.x/b,a.y/b);}
向量的点积
这个学过高中数学必修四的应该都知道。
比如说对于\(\vec a\)和\(\vec b\),集合上的定义为:\(\vec a\times \vec b\times cos \theta\) ,代数的计算方法则为\(ax*bx+ay*by\)。
inline double dot(vec a,vec b){return a.x*b.x+a.y*b.y;}
向量的长度
inline double length(vec a){return sqrt(dot(a,a));}
向量的夹角(返回值为弧度制)
inline double angle(vec a,vec b){return acos(dot(a,b)/length(a)/length(b));}
向量的叉积
对于\(\vec a\)和\(\vec b\),集合上的定义为:\(\vec a\times \vec b\times sin \theta\) ,代数的计算方法则为\(ax*by-ay*bx\)。
它的几何意义表示这两个向量形成的平行四边形的面积。
inline double cross(vec a,vec b){return a.x*b.y-a.y*b.x;}
因为叉积在数值上可以表示向量a,b围成的平行四边形的面积,所以我们可以通过叉积来计算三个点围成的三角形的面积——
inline double area(point a,point b,point c){return cross(b-a,c-a);}
除此之外还有一些应用:
向量的旋转(注意是逆时针旋转)
公式为\(x_{new}=xcosa-ysina\),\(y_{new}=xsina+ycosa\),其中a为逆时针旋转的角的弧度值。
inline vec rotate(vec x,double a)
{return vec(x.x*cos(a)-x.y*sin(a),x.x*sin(a)+x.y*cos(a));}
直线的交点
inline point getline_intersection(point p,vec v,point q,vec w)
{
vec u=p-q;
double t=cross(w,u)/cross(v,w);
return p+mul(v,t);
}
点到直线的距离
其实数学上有个公式叫做\(\frac{fabs(ax+by+c)}{\sqrt {k^2+1}}\),但是这个需要直线的表达式,我们可以利用叉积来更方便地进行计算。( 也就是用平行四边形的面积除以底)
inline double distance(point p,point a,point b)//a,b组成的是边
{
vec v1=b-a,v2=p-a;
return fabs(cross(v1,v2))/length(v1);//如果不取绝对值,得到的是有向距离
}
多边形的计算(同时适用于凸多边形,凹多边形)
其实就是从一个顶点开始,将多边形划分成若干个三角形进行计算。(从p0开始划分)
inline double Area(point* p,int n)
{
double cur_area=0
for(int i=1;i<n-1;i++)
cur_area+=cross(p[i]-p[0],p[i+1]-p[0]);
return cur_area/2;
}
线段相交判定
inline bool whether_intersection(point a1,point a2,point b1,point b2)
{
double c1=cross(a2-a1,b1-a1);
double c2=cross(a2-a1,b2-a1);
double c3=cross(b2-b1,a2-b1);
double c4=cross(b2-b1,a1-b1);
return (dcmp(c1)*dcmp(c2)<0)&&(dcmp(c3)*dcmp(c4)<0);
}
多边形的重心
把一个多边形剖分成很多的三角形,依次给每个三角形求重心,按照面积加权求平均就是该多边形的重心。
判断一个点是否在多边形内
从该点随便引(一般都是竖直吧qwq)一条射线,计算它与多边形所有边的交点的数目,如果是奇数个就在多边形内,如果是偶数个就在多边形外。(在线段的两端点上相交不算有交点)
但是要注意一下细节
求两圆的交点
求两圆的内(外)公切线
如果一条直线和两个圆都相切,这条直线叫做两个圆的公切线。如果两圆在公切线的同侧,称这条公切线为两圆的外公切线,如果两圆分别在公切线的两侧,称这条公切线为两圆的内公切线。
求凸包
懒得省事了,直接放一个模板好了qwqwq
inline bool cmp(struct Node a,struct Node b)
{
double A=atan2((a.y-t[1].y),(a.x-t[1].x));
double B=atan2((b.y-t[1].y),(b.x-t[1].x));
if(A!=B) return A<B;
else return a.x<b.x;
}
inline double cross(Node a,Node b,Node c){return (a.x-c.x)*(b.y-c.y)-(a.y-c.y)*(b.x-c.x);}
inline void solve()
{
t[0]=(Node){0x3f3f3f3f,0x3f3f3f3f};
int k=0;
for(int i=1;i<=n;i++)
if(t[i].y<t[0].y||(t[i].y==t[0].y&&t[i].x<t[0].x))
t[0]=t[i],k=i;
swap(t[1],t[k]);
sort(&t[2],&t[1+n],cmp);
s[0]=t[1],s[1]=t[2];
top=1;
for(int i=3;i<=n;i++)
{
while(top&&cross(s[top-1],t[i],s[top])>=0.0) top--;
s[++top]=t[i];
}
}
动态凸包:(请看我的这篇博客)
关键代码:(我这里使用的是极角排序)
inline void solve(Node x)
{
nxt=st.lower_bound(x);
if(nxt==st.end()) nxt=st.begin();
pre=get_pre(nxt);
if(cross((*nxt)-(*pre),x-(*pre))>=0) return;
cur_ans-=get_len((*nxt)-(*pre));
cur_ans+=get_len(x-(*nxt))+get_len(x-(*pre));
st.insert(x);
tmp=get_pre(pre);
while(cross(x-(*pre),(*pre)-(*tmp))>=0)
{
cur_ans-=get_len(x-(*pre))+get_len((*pre)-(*tmp));
cur_ans+=get_len(x-(*tmp));
st.erase(pre);
pre=tmp;
tmp=get_pre(pre);
}
tmp=get_nxt(nxt);
while(cross(x-(*nxt),(*nxt)-(*tmp))<=0)
{
cur_ans-=get_len(x-(*nxt))+get_len((*nxt)-(*tmp));
cur_ans+=get_len(x-(*tmp));
st.erase(nxt);
nxt=tmp;
tmp=get_nxt(nxt);
}
return;
}
点在多边形内的判定
method1 射线法
就是从某一个判定点出发,任意引出一条射线。如果和边界相交奇数次,说明点在多边形内。如果相交偶数次,说明点在多边形外。注意射线如果在端点处和多边形相交,或者穿过一条完整的边,则需要重新引出一条射线QAQ
method2 转角法
我们把多边形的每条边的转角加起来,如果是360度,说明在多边形内。如果是0度,说明在多边形外。如果是180度,说明在多边形的边界上。优化的操作如下:假想有一条向右的射线,统计多边形穿过这条射线正反多少次,把这个数基座绕数,逆时针穿过时+1,顺时针穿过时-1.
旋转卡壳
半平面交
咕咕咕