计算几何《简单》入土芝士
文章目录
- 参照文献
- 点、向量
- 书写约定
- 向量运算
- 普通运算
- 叉积
- 多边形的面积
- 线段的相交与直线的交点
- 线段的相交判定
- 直线交点
- 定理1
- 运用定理1优化
- 更加广泛的情况
- 凸包
- 题目
- 思路
- 凸包初步概念
- 转换思路
- 凸包求法
- 凸包的性质
- 凸包的求法
- 代码
- 课内可能会用到的数学知识福利
- 点到直线的距离
- 如何将直线移动1个单位的距离?
- 两直线交点看不懂版
- 1
- 2
- 小结
参照文献
你谷日报真好用:https://www.luogu.com.cn/blog/wjyyy/geometry1
B站UP主:https://space.bilibili.com/88461692?spm_id_from=333.788.b_765f7570696e666f.1
https://www.cnblogs.com/xiexinxinlove/p/3708147.html
点、向量
书写约定
上了初中就应该学过直角坐标系了,这里不多阐述,本文(x,y)(x,y)(x,y)表示点的坐标,A(x,y)A(x,y)A(x,y)中的AAA表示点的编号,特殊的,OOO点是坐标系的圆点。
向量呢,是一个有向线段,通常表示为:AB⃗\vec{AB}AB就表示由AAA到BBB的有向线段。
但是呢,许多情况,向量默认起点是在坐标系的原点:OOO,因此向量经常看到的形式都是:OA⃗\vec{OA}OA,且由于起点固定,所以向量还有种表示方法,就是直接用终点AAA来表示向量,[xAyA]\begin{bmatrix}x_A\\y_A\end{bmatrix}[xAyA]也是一种表示方法。
下文中一般情况下默认起点不一定在OOO点(很大多数情况都是),但是当看到用终点表示向量时,默认起点是OOO或者是前文规定的某个基准点(表示以这个点建立坐标系),如果一般是AB⃗\vec{AB}AB这样两个端点给出的向量乘积,那么就以两个向量的共同端点为基准点建立坐标系。(当然还是有特例,一般会有特殊说明可能会忘记写)
向量的模呢,其实就是其的长度,比如向量A⃗\vec{A}A的模写作:∣A⃗∣|\vec{A}|∣A∣。
向量的幅角呢,其实就是其和xxx正半轴形成的夹角。
一般可以用atan2atan2atan2函数进行计算,但是需要注意C++C++C++用的是弧度制。(其实没啥区别,正负性没有变,值域变了而已。)
向量运算
实际上,这些运算实际指的是起点在OOO点的运算。
普通运算
加法运算:[xAyA]+[xByB]=[xA+xByA+yB]\begin{bmatrix}x_A\\y_A\end{bmatrix}+\begin{bmatrix}x_B\\y_B\end{bmatrix}=\begin{bmatrix}x_A+x_B\\y_A+y_B\end{bmatrix}[xAyA]+[xByB]=[xA+xByA+yB]
减法运算:[xAyA]−[xByB]=[xA−xByA−yB]\begin{bmatrix}x_A\\y_A\end{bmatrix}-\begin{bmatrix}x_B\\y_B\end{bmatrix}=\begin{bmatrix}x_A-x_B\\y_A-y_B\end{bmatrix}[xAyA]−[xByB]=[xA−xByA−yB]
乘常数运算:[xAyA]∗w=[xA∗wyA∗w]\begin{bmatrix}x_A\\y_A\end{bmatrix}*w=\begin{bmatrix}x_A*w\\y_A*w\end{bmatrix}[xAyA]∗w=[xA∗wyA∗w]
其余的运算对于信息学计算几何用处不大,因此不再赘述。
你放屁,叉积不重要吗。
我也知道叉积重要
所以下面开始讲叉积
叉积
叉积的运算法则:[xByB]∗[xAyA]=xB∗yA−yB∗xA\begin{bmatrix}x_B\\y_B\end{bmatrix}*\begin{bmatrix}x_A\\y_A\end{bmatrix}=x_B*y_A-y_B*x_A[xByB]∗[xAyA]=xB∗yA−yB∗xA(考虑到图片标识问题,所以ABABAB换个位置。)
你没有看错,就是这么个东西,返回值竟然还是实数,甚至还没有交换律(因此叉积一定要注意前后顺序)。
那么其有什么实际作用呢?
对于向量OB⃗\vec{OB}OB和OA⃗\vec{OA}OA,如果OB⃗\vec{OB}OB绕OOO点逆时针旋转0°0°0°~180°180°180°能够与OA⃗\vec{OA}OA共线,那么OB⃗∗OA⃗>0\vec{OB}*\vec{OA}>0OB∗OA>0,但是如果不能,代表OA⃗\vec{OA}OA逆时针旋转0°0°0° ~ 180°180°180°能够与BBB重合,那么OB⃗∗OA⃗<0,OB⃗∗OA⃗>0\vec{OB}*\vec{OA}<0,\vec{OB}*\vec{OA}>0OB∗OA<0,OB∗OA>0,当然,如果一开始就共线,那么为000。
至于叉积还有什么作用,也就是求▲OAB▲OAB▲OAB的面积了。
实际上,这其实是叉积在线性代数中的意义,叉积求的是OB⃗\vec{OB}OB和OA⃗\vec{OA}OA为边的平行四边形的面积(除222即可),但是为什么会有正负之分呢?额,这个跟线性变换有关,不再赘述,反正意义差不多就这两个。
额,至于证明吗,其实用几何就能证明了,至于线性代数的证明呢?我也不知道。可能根本就没有吧。
同机房的CLB奆佬用割补法给出了证明,但是实际上和上面的本质是差不多的,在此不再赘述。
当然,叉积的记忆也很简单。
交叉相乘再相减。
inline double mu(dian x,dian y,dian z)//以点z为圆点的叉积
{double x1=(x.x-z.x),y1=(x.y-z.y),x2=(y.x-z.x),y2=(y.y-z.y);return x1*y2-x2*y1;
}
当然,叉积有一个性质大家应该也看出来了:A⃗∗B⃗=−B⃗∗A⃗\vec{A}*\vec{B}=-\vec{B}*\vec{A}A∗B=−B∗A
多边形的面积
【题意】
在一个平面坐标系上随意画一条有n个点的封闭折线(按画线的顺序给出点的坐标),保证封闭折线的任意两条边都不相交。最后要计算这条路线包围的面积。
(注意,不能相交)
【输入格式】
第一行整数 n (3 <= n <= 1000),表示有n个点。
下来n行,每行两个整数x(横坐标)和y(纵坐标),表示点坐标(-10000<x,y<=10000)。
【输出格式】
一行一个实数,即封闭折线所包围的面积(保留4位小数)。
【样例1输入】
4
2 1
5 1
5 5
2 5
【样例1输出】
12.0000【样例2输入】
5
2 1
5 1
3 2
5 3
2 3
【样例2输出】
4.0000
先考虑凸多边形:
只要这样子分就行了
可以看出只要选一个点然后以这个点分割成一堆三角形就行了,而且用叉积都是一个方向。
公式的话,设点的序列是从某个顶点开始,逆时针给出,为a1,a2,a3,...,aka_1,a_2,a_3,...,a_ka1,a2,a3,...,ak,那么就为(a1a2⃗∗a1a3⃗+a1a3⃗∗a1a4⃗+...+a1ak−1⃗∗a1ak⃗)/2(\vec{a_1a_2}*\vec{a_1a_3}+\vec{a_1a_3}*\vec{a_1a_4}+...+\vec{a_1a_{k-1}}*\vec{a_1a_k})/2(a1a2∗a1a3+a1a3∗a1a4+...+a1ak−1∗a1ak)/2。
需要注意的是,由于是逆时针给出,且是正数,所以其中的每一个叉积都是正数,不需要加绝对值,但是如果在不知道是逆时针还是顺时针给出的情况下,最好还是加上绝对值。
那么我们再考虑非凸多边形面积的情况:
如果采用上述的方法:(a1a2⃗∗a1a3⃗+a1a3⃗∗a1a4⃗+...+a1ak−1⃗∗a1ak⃗)/2(\vec{a_1a_2}*\vec{a_1a_3}+\vec{a_1a_3}*\vec{a_1a_4}+...+\vec{a_1a_{k-1}}*\vec{a_1a_k})/2(a1a2∗a1a3+a1a3∗a1a4+...+a1ak−1∗a1ak)/2,我们发现刚好搞好就是多边形的面积,而有些叉积虽然算到多边形外面,后来又消掉。
因此你会发现,如果每次叉积都取绝对值反而会错,因此叉积的正负性真的是一个非常美妙的东西啊。
证明完全不会。
而在网上寻找证明的时候,我看到一个有意思的东西。还是没找到证明
就是说我们上文是默认a1a_1a1为三角形的公用顶点的,但是如果我们在坐标系中随便选一个点xxx,然后值为:(xa1⃗∗xa2⃗+...+xak⃗∗xa1⃗)/2(\vec{xa_1}*\vec{xa_2}+...+\vec{xa_k}*\vec{xa_1})/2(xa1∗xa2+...+xak∗xa1)/2,这样竟然也不会错,用一张别的大佬的博客的图来帮助理解。
#include<cstdio>
#include<cstring>
#define N 1100
using namespace std;
int n;
struct node{double x,y;}list[N];
double mu(node x,node y,node z)
{double x1=(x.x-z.x),y1=(x.y-z.y),x2=(y.x-z.x),y2=(y.y-z.y);return x1*y2-x2*y1;
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%lf%lf",&list[i].x,&list[i].y);double ans=0;for(int i=3;i<=n;i++)ans+=mu(list[i],list[i-1],list[1]);//直接求 if(ans<0)ans=-ans;//题目并没有给出顺时针还是逆时针。printf("%.4lf",ans/2.0/*三角形面积要除以2*/);return 0;
}
线段的相交与直线的交点
线段的相交判定
【题意】
有n条线段(编号为1~n),按1~n的顺序放在二维坐标系上(就是先放1号,再放2号……),
要求输出最上面的那些线段的编号(就是没有其他线段压在它上面的那些线段)
【输入格式】
第一行第一个数n( 1 <= n <= 10000)表示这组数据有n条线段。
下来n行,每行两个坐标,表示第i条线段的两个端点。
【输出格式】
一行。输出最上面的线段的编号(从小到大)。相邻两个编号用空格隔开,最后一个编号没有空格。
【样例1输入】
5
1 1 4 2
2 3 3 1
1 -2.0 8 4
1 4 8 2
3 3 6 -2.0
【样例1输出】
2 4 5
【样例2输入】
3
0 0 1 1
1 0 2 1
2 0 3 1
【样例2输出】
1 2 3
线段相交裸题。
线段相交不直接列方程打几十行代码暴力判断吗
首先看下面这张图:
把向量ABABAB化成直线,然后判断另外一个向量是否是C′′⃗D′′\vec{C''}{D''}C′′D′′的形式,其余的两种形式肯定都不可能相交。
敏锐的人发现了端点一个在左边,一个在右边(当然,其中一个端点在直线上也可以,两个端点等会讲),更加敏锐的人发现,直线不就是180°180°180°吗?特别敏锐的人发现,只要两条向量互相都满足这个要求,则一定相交,在不共线的情况下相交,则两条向量互相都满足这个要求。
至于怎么判断是不是长这个样子,即(AB⃗∗AC′′⃗)∗(AB⃗∗AD′′⃗)<0(\vec{AB}*\vec{AC''})*(\vec{AB}*\vec{AD''})<0(AB∗AC′′)∗(AB∗AD′′)<0,然后两个一起判断即可。
代码片段:
if(mu(y.x,x.x,x.y)*mu(y.y,x.x,x.y)<=0 && mu(x.x,y.x,y.y)*mu(x.y,y.x,y.y)<=0)return true;
注:此代码在后面会稍有魔改,本质是利用叉积:A⃗∗B⃗=−B⃗∗A⃗\vec{A}*\vec{B}=-\vec{B}*\vec{A}A∗B=−B∗A的性质,而且<=0<=0<=0后面要把等号去掉000,因为端点在直线上和共线都是000,不能区分。
但是呢,还有一种特别特别恶心的情况,共线。
共线的时候,两个既有可能相交,又有可能不相交,但是由于已经共线,所以我们不妨直接暴力判断即可。
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 21000
using namespace std;
struct dian{double x,y;};
struct bian{dian x,y;}list[N];
int n;
inline double mymin(double x,double y){return x<y?x:y;}
inline double mymax(double x,double y){return x>y?x:y;}
inline double mu(dian x,dian y,dian z)//x->y
{double x1=(x.x-z.x),y1=(x.y-z.y),x2=(y.x-z.x),y2=(y.y-z.y);return x1*y2-x2*y1;
}
inline bool pd(dian x1,dian x2,dian y1){return (mymin(x1.x,x2.x)<=y1.x && mymax(x1.x,x2.x)>=y1.x && mymin(x1.y,x2.y)<=y1.y && mymax(x1.y,x2.y)>=y1.y);}//判断y1点是否在线段x1-x2上
inline bool check(bian x,bian y)
{if(mu(y.x,x.x,x.y)*mu(x.x,y.y,x.y)>0 && mu(x.x,y.x,y.y)*mu(y.x,x.y,y.y)>0)return true;//普通情况if((mu(x.x,x.y,y.x)==0 && pd(x.x,x.y,y.x)) ||(mu(x.x,x.y,y.y)==0 && pd(x.x,x.y,y.y)) ||(mu(y.x,y.y,x.x)==0 && pd(y.x,y.y,x.x)) ||(mu(y.x,y.y,x.y)==0 && pd(y.x,y.y,x.y)))return true;//暴力判断不同的相交情况return false;
}
bool bo[N];
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%lf%lf%lf%lf",&list[i].x.x,&list[i].x.y,&list[i].y.x,&list[i].y.y);for(int i=1;i<=n;i++){for(int j=i+1;j<=n;j++){if(check(list[i],list[j])){bo[i]=true;break;}}}//O(n^2)暴力判for(int i=1;i<=n;i++){if(!bo[i])printf("%d ",i);}printf("\n");return 0;
}
直线交点
注:本文不考虑共线
一个平面的直线除了平行一定有交点。
平行的情况这里不讲(实际上用截距法算一下斜率判断一下即可),但是这里要将如何在知道两条直线上的各两个点(一条直线上给出的两个点不会相同)的情况下(为什么不能知道斜率啊(#`O′)),判断其交点。
都知道两点确定一条直线,于是可以直接求斜率列方程求交点,但是呢,这样是不是过于麻烦了呢?
于是我们考虑用叉积来求。其实就是想装逼
现在,我们不管两个点形成的直线,而是考虑其形成的线段。
先考虑给出的线段ABABAB在直线CDCDCD的两侧(C,DC,DC,D点都为直线上任意一点):
注:如果你把给出的线段当成向量,那么A,DA,DA,D为起点。
这里先默认AAA在BBB左边,CCC在DDD上面,保证叉积是正的。
此时AB=AP+BPAB=AP+BPAB=AP+BP。
如果我们能求出∣AP∣∣BP∣\frac{|AP|}{|BP|}∣BP∣∣AP∣,那么就可以用下面这条公式算出PPP的坐标:
OA⃗+∣AP∣∣AB∣∗AB⃗=OP⃗\vec{OA}+\frac{|AP|}{|AB|}*\vec{AB}=\vec{OP}OA+∣AB∣∣AP∣∗AB=OP,当然,这不就是PPP的坐标吗?
但是关键是怎么求∣AP∣∣AB∣\frac{|AP|}{|AB|}∣AB∣∣AP∣呢?
考虑转换成面积,A、BA、BA、B向CDCDCD做一条垂线。
不难看出:▲AEP∽▲BFP▲AEP∽▲BFP▲AEP∽▲BFP
所以APBP=AEBF=S▲ABDS▲BCD\frac{AP}{BP}=\frac{AE}{BF}=\frac{S▲ABD}{S▲BCD}BPAP=BFAE=S▲BCDS▲ABD
完美的转换成两个三角形的比值。
因此考虑叉积,以DDD为基准点:
APAB=DC⃗∗DA⃗DB⃗∗DC⃗+DC⃗∗DA⃗\frac{AP}{AB}=\frac{\vec{DC}*\vec{DA}}{\vec{DB}*\vec{DC}+\vec{DC}*\vec{DA}}ABAP=DB∗DC+DC∗DADC∗DA
当然,上文中DDD在CCC下面,且AAA在BBB左边,所以叉积一直都是正的,当然,如果CCC在DDD的下面,则所有叉积取负,反正求的是比例,也没什么事,如果AAA在BBB的右边,也是如此,只是正负性的变化,但是因为同时变化,所以没有关系。
而且我们发现因为叉积是求逆时针的,所以旋转并不会改变正负性,只有左右翻折会(且是所有叉积的正负性都会改变,因此左右翻折不会改变叉积之间比例),而上文中,AAA与BBB互换位置其实就是翻折,CCC在DDD下面确不是完全的翻折,但是CCC在DDD下面也会导致所求的叉积全部取负,所以也不会改变比例。
当然,这个做法分母不够优秀,因此我们来证明一个定理。
定理1
AB⃗∗AC⃗+AC⃗∗AD⃗=AC⃗∗BD⃗\vec{AB}*\vec{AC}+\vec{AC}*\vec{AD}=\vec{AC}*\vec{BD}AB∗AC+AC∗AD=AC∗BD
为什么这两个向量公共端点没有也能乘。
这就是前面叉积讲解中忽略的第三种情况。
对于AC⃗\vec{AC}AC,我们用CCC的坐标减去AAA的坐标,相当于把AC⃗\vec{AC}AC移到O?⃗\vec{O?}O?的位置,把起点设为OOO,然后两个向量都如此操作后得到的叉积值。
或者你也可以理解为通过同样的方式把一个向量的起点移到另外一个向量的起点,以此统一起点。
而在这个直线交点的证明的后半部分,基本都是用这种方式,于是又多了一个性质:
AB⃗∗CD⃗=−BA⃗∗CD⃗\vec{AB}*\vec{CD}=-\vec{BA}*\vec{CD}AB∗CD=−BA∗CD
证明的话,同底等高的三角形面积相同,且由于方向问题,取个符号。
于是不管是叉积的顺序还是向量内部的顺序,都显得十分重要。
回归正题。
证明:
(xB−xA)(yC−yA)−(yB−yA)(xC−xA)+(xC−xA)(yD−yA)−(yC−yA)(xD−xA)(x_{B}-x_{A})(y_{C}-y_{A})-(y_{B}-y_{A})(x_{C}-x_{A})+(x_{C}-x_{A})(y_{D}-y_{A})-(y_{C}-y_{A})(x_{D}-x_{A})(xB−xA)(yC−yA)−(yB−yA)(xC−xA)+(xC−xA)(yD−yA)−(yC−yA)(xD−xA)
=(xB−xA)(yC−yA)−(yC−yA)(xD−xA)−(yB−yA)(xC−xA)+(xC−xA)(yD−yA)=(x_{B}-x_{A})(y_{C}-y_{A})-(y_{C}-y_{A})(x_{D}-x_{A})-(y_{B}-y_{A})(x_{C}-x_{A})+(x_{C}-x_{A})(y_{D}-y_{A})=(xB−xA)(yC−yA)−(yC−yA)(xD−xA)−(yB−yA)(xC−xA)+(xC−xA)(yD−yA)
=(xB−xD)(yC−yA)−(yB−yD)(xC−xA)=(x_{B}-x_{D})(y_{C}-y_{A})-(y_{B}-y_{D})(x_{C}-x_{A})=(xB−xD)(yC−yA)−(yB−yD)(xC−xA)
=(yD−yB)(xC−xA)−(xD−xB)(yC−yA)=(y_{D}-y_{B})(x_{C}-x_{A})-(x_{D}-x_{B})(y_{C}-y_{A})=(yD−yB)(xC−xA)−(xD−xB)(yC−yA)
=AC⃗∗BD⃗=\vec{AC}*\vec{BD}=AC∗BD
可视化理解:
运用定理1优化
∣AP∣∣AB∣=DC⃗∗DA⃗DB⃗∗DC⃗+DC⃗∗DA⃗=DC⃗∗DA⃗DC⃗∗BA⃗=DC⃗∗DA⃗AB⃗∗DC⃗\frac{|AP|}{|AB|}=\frac{\vec{DC}*\vec{DA}}{\vec{DB}*\vec{DC}+\vec{DC}*\vec{DA}}=\frac{\vec{DC}*\vec{DA}}{\vec{DC}*\vec{BA}}=\frac{\vec{DC}*\vec{DA}}{\vec{AB}*\vec{DC}}∣AB∣∣AP∣=DB∗DC+DC∗DADC∗DA=DC∗BADC∗DA=AB∗DCDC∗DA
更加广泛的情况
当然,还有A,BA,BA,B在直线同侧的情况,
同样,先保证DC⃗∗DA⃗\vec{DC}*\vec{DA}DC∗DA是正的,且BBB在AAA右边,CCC在DDD上面。
此时AB=AP−BPAB=AP-BPAB=AP−BP,一样可以APBP=AEBF=S▲ABDS▲BCD\frac{AP}{BP}=\frac{AE}{BF}=\frac{S▲ABD}{S▲BCD}BPAP=BFAE=S▲BCDS▲ABD
但是我们发现−BP-BP−BP难搞啊,但是DB⃗∗DC⃗\vec{DB}*\vec{DC}DB∗DC就是负的,完美,这种情况证毕。
于是考虑翻折和旋转,我们只要证明了A,BA,BA,B在直线C,DC,DC,D的左边的情况是成立的,旋转便可以得到所有的情况。
很明显,C,DC,DC,D的上下关系改变所有的叉积正负性,比例不变,但是BBB在AAA左边时,我们发现,正负性都没有改变,应该没有影响吧,但是算出来的APAB\frac{AP}{AB}ABAP变成了负数(但比例绝对值对的)?但是发现AB⃗\vec{AB}AB与AP⃗\vec{AP}AP方向反了,负数刚好调过来,完美,因此得证。
考场直接记住结论他不香吗。
实际上,你从假设给出的两条线段有交点的方向推出的公式,基本上是可以推广到所有情况的,在考场上忘了直接这样推就行了(记住推的思路是最好的),当然不排除推广不了的情况。
inline double muu(dian x1,dian x2){return x1.x*x2.y-x2.x*x1.y;}//叉积
inline dian jd(line x,line y)
{double w =muu((y.y-y.x),(x.x-y.x))/muu((x.y-x.x),(y.y-y.x));return x.x+(x.y-x.x)*w;
}
凸包
题目
【题意】
在一个平面坐标系上有n个点,用笔画一个多边形,使得多边形包含这n个点(点在多边形的边上也算包含)。
求多边形的最小周长。
【输入格式】
第一行整数 n (1 <= n <= 1000),表示有n个点。
下来n行,每行两个整数x(横坐标)和y(纵坐标),表示点坐标(-10000<=x,y<=10000)。
【输出格式】
一行一个实数,即多边形的最小周长(保留4位小数)。
【样例1输入】
4
2 1
5 1
5 5
2 5
【样例1输出】
14.0000【样例2输入】
5
2 1
5 1
3 2
5 3
2 3
【样例2输出】
10.0000
思路
凸包初步概念
即找到最小的凸多边形包括我们给出点集VVV。
转换思路
不难发现凸包就是最小周长:
非凸多边形很明显可以用三角形两边之和小于第三边来证明。
其余的凸多边形,一定比凸包周长要大,感性的理解就是拿个橡皮筋,在一个图上放手,会发现其就是凸包,且是最小周长,但是理性证明,不会。
凸包求法
Orz wjyyy奆佬手推凸包。
但是这里不讲上下凸包合并。
讲一个算法:graham扫描法。
凸包的性质
介绍这种算法不妨从凸包的性质讲起。
- 点集的最下角(即yyy最小,yyy相同xxx最小)的点一定是凸包的顶点,设这个点为xxx,不难发现对于点集中任意一点y(y≠x)y(y≠x)y(y=x),xy⃗\vec{xy}xy的幅角都非负。
- 从xxx逆时针遍历凸包的顶点,得到aaa顶点序列(kkk个点),其中a1=x,ai=a(i−1)%k+1a_1=x,a_{i}=a_{(i-1)\%k+1}a1=x,ai=a(i−1)%k+1,xa2⃗,xa3⃗,...,xak⃗\vec{xa_2},\vec{xa_3},...,\vec{xa_k}xa2,xa3,...,xak的幅角严格递增,且都非负。
- 顶点序列中对于(1≤i≤k)(1≤i≤k)(1≤i≤k),∠aiai+1ai+2<180°∠a_{i}a_{i+1}a_{i+2}<180°∠aiai+1ai+2<180°,aiai+1⃗∗aiai+2⃗>0\vec{a_{i}a_{i+1}}*\vec{a_{i}a_{i+2}}>0aiai+1∗aiai+2>0(注意这条,如果对于一个多边形满足这条公式,其是凸多边形应该,至少凸包就是用这个判断的。)。
不要问我证明,我也不会证明,再问就是显然
凸包的求法
根据第一条性质,找到一个点,并且把其放到第一个点,在把剩余的点,按照其和第一个点形成的向量的幅角排序(幅角相同,长度小的优先)。
这样排序有个什么好处呢?这样保证了排序中最后那个点一定在凸包中。
用栈维护目前凸包的点序列,设现在栈中有toptoptop个点,现在新加入xxx点,如果top=1top=1top=1,无条件假如,如果不是,atop−1atop⃗∗atop−1x⃗>0\vec{a_{top-1}a_{top}}*\vec{a_{top-1}x}>0atop−1atop∗atop−1x>0,加入,否则atopa_{top}atop在凸包的边上或者内部,弹出,然后不断判断,知道xxx可以加入为止。
代码
时间复杂度:O(nlogn)O(nlogn)O(nlogn)。
几年前的代码好难看啊。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n;
struct node
{double x,y;
}a[2100];double ans;
double ins(node x,node y){return sqrt(double((x.x-y.x)*(x.x-y.x))+double((x.y-y.y)*(x.y-y.y)));}
double mutle(node x,node y,node z/*基准点*/)//叉积
{double x1=x.x-z.x,y1=x.y-z.y,x2=y.x-z.x,y2=y.y-z.y;return x1*y2-x2*y1;
}
bool cmp(node x,node y)
{double okk=mutle(y,x,a[1]);//反正幅角都是非负的,这样判断也可以。if(okk>0)return true;else if(okk<0)return false;else{double k1=ins(x,a[1]);double k2=ins(y,a[1]);if(k1>k2)return false;else return true;}
}
node list[2100];
int top;
node work()
{sort(a+2,a+n+1,cmp);top=2;list[1]=a[1];list[2]=a[2];for(int i=3;i<=n;i++){while(top>1 && mutle(list[top],a[i],list[top-1])>0)top--;list[++top]=a[i];}
}
int main()
{scanf("%d",&n);int x,y;scanf("%d%d",&x,&y);a[1].x=x*1.0;a[1].y=y*1.0;for(int i=2;i<=n;i++){scanf("%d%d",&x,&y);a[i].x=x*1.0;a[i].y=y*1.0;if(a[i].y<a[1].y || (a[i].y==a[1].y && a[i].x<a[1].x)){node t=a[i];a[i]=a[1];a[1]=t;}}work();for(int i=2;i<=top;i++)ans+=ins(list[i],list[i-1]);ans+=ins(list[1],list[top]);printf("%.4lf\n",ans);return 0;
}
课内可能会用到的数学知识福利
点到直线的距离
这里放上一个大佬的另类证法,很强!
大佬
膜拜大佬。
如何将直线移动1个单位的距离?
这里指的是垂直距离,那么就不是+1-1的事情了,这里用两点式来进行证明其中的一种情况(其他情况也可以证)
现在有一条直线:ax+by=0(a<0,b>0)ax+by=0(a<0,b>0)ax+by=0(a<0,b>0)
AC⊥AAC⊥AAC⊥A所在的直线
AC=1AC=1AC=1,设CCC点的xxx坐标为kkk,那么我们可以求出ACACAC的两点时bx−ay=0bx-ay=0bx−ay=0,那么进而求出AD=−bxaAD=-\frac{bx}{a}AD=−abx,勾股得出AC=xa2+b2−a=1AC=\frac{x\sqrt{a^{2}+b^{2}}}{-a}=1AC=−axa2+b2=1,那么x=−aa2+b2a2+b2x=\frac{-a\sqrt{a^{2}+b^{2}}}{a^{2}+b^{2}}x=a2+b2−aa2+b2,那么C(−aa2+b2a2+b2,−ba2+b2a2+b2)C(\frac{-a\sqrt{a^{2}+b^{2}}}{a^{2}+b^{2}},\frac{-b\sqrt{a^{2}+b^{2}}}{a^{2}+b^{2}})C(a2+b2−aa2+b2,a2+b2−ba2+b2),然后我们通过将x,yx,yx,y带入ax+by+?ax+by+?ax+by+?求出???是a2+b2\sqrt{a^{2}+b^{2}}a2+b2,那么我们就知道移动1就是加减a2+b2\sqrt{a^{2}+b^{2}}a2+b2。
两直线交点看不懂版
现在我是看不懂我以前写什么了。而且这个证法好像还不严谨
但是本着保留思想的心态,我把其保留了下来,可以不看,完全没有看的必要。毕竟我自己写的东西自己也没看看懂
难道我们求两线交点就只能列一个二元一次方程?
不,我们可以用叉积!!!
看如下图:
如何求APCP\frac{AP}{CP}CPAP?
我们做一条垂线:
我们AEBF=S▲ABDS▲CBD\frac{AE}{BF}=\frac{S▲ABD}{S▲CBD}BFAE=S▲CBDS▲ABD,∠APE=∠CPF,∠AEP=∠CFP∠APE=∠CPF,∠AEP=∠CFP∠APE=∠CPF,∠AEP=∠CFP,所以▲AEP∽▲CFP▲AEP∽▲CFP▲AEP∽▲CFP,所以APCP=AECF=S▲ABDS▲CBD\frac{AP}{CP}=\frac{AE}{CF}=\frac{S▲ABD}{S▲CBD}CPAP=CFAE=S▲CBDS▲ABD
而这个面积是可以用叉积来求的。
我们设t1,t2t1,t2t1,t2分别为以CCC为基准点旋到A,BA,BA,B的叉积。
两种情况:
1
此处t1<0,t2>0t1<0,t2>0t1<0,t2>0
PPP点坐标?
列波式子(求xxx坐标,yyy坐标一样,在这里推导过程省略原因,多用相似三角形):
x1+−(x2−x1)∗t1t2−t1x1+\frac{-(x2-x1)*t1}{t2-t1}x1+t2−t1−(x2−x1)∗t1
=x1(t2−t1)−(x2−x1)∗t1t2−t1=\frac{x1(t2-t1)-(x2-x1)*t1}{t2-t1}=t2−t1x1(t2−t1)−(x2−x1)∗t1
=x1∗t2−x2∗t1t2−t1=\frac{x1*t2-x2*t1}{t2-t1}=t2−t1x1∗t2−x2∗t1
=t1∗x2−t2∗x1t1−t2=\frac{t1*x2-t2*x1}{t1-t2}=t1−t2t1∗x2−t2∗x1
当然,死记硬背也没问题:122112122112122112。
2
那么这个的长这样:
在此t1<0,t2<0t1<0,t2<0t1<0,t2<0
x1+−(x2−x1)∗t1t2−t1x1+\frac{-(x2-x1)*t1}{t2-t1}x1+t2−t1−(x2−x1)∗t1
=x1(t2−t1)−(x2−x1)∗t1t2−t1=\frac{x1(t2-t1)-(x2-x1)*t1}{t2-t1}=t2−t1x1(t2−t1)−(x2−x1)∗t1
=x1∗t2−x2∗t1t2−t1=\frac{x1*t2-x2*t1}{t2-t1}=t2−t1x1∗t2−x2∗t1
=t1∗x2−t2∗x1t1−t2=\frac{t1*x2-t2*x1}{t1-t2}=t1−t2t1∗x2−t2∗x1
等会,这个很眼熟!!!
跟上面一样,所以用这个就行了。
inline dian jd(line x,line y)//怎么可能用inline
{double t1=mu(x.x,y.x,y.y),t2=mu(x.y,y.x,y.y);return dian((t1*x.y.x-t2*x.x.x)/(t1-t2),(t1*x.y.y-t2*x.x.y)/(t1-t2));
}
小结
这还仅仅是入门啊QAQ。
计算几何《简单》入土芝士相关推荐
- POJ 1066 Treasure Hunt(计算几何)
题意:给出一个100*100的正方形区域,通过若干连接区域边界的线段将正方形区域分割为多个不规则多边形小区域,然后给出宝藏位置,要求从区域外部开辟到宝藏所在位置的一条路径,使得开辟路径所需要打通的墙壁 ...
- 芝士Java实现的图书管理系统
本图书管理系统用对象数组的方式来提供操作方法,比较特别,建议新手学习,这对理解Java面向对象有很大帮助 目录 User类 管理员和普通用户类 Book类 BookList类 Operate类 退出系 ...
- 【全栈软件测试】软件测试学习路线介绍
一.前言 1.为何要写软件测试,软件测试很简单. 实际上,软件测试入门简单,但要学透学好,是有很多知识的,入门简单入土难.当你看完学习路线,就知道写的是全栈软件测试,涵盖的内容:全栈软件测试,从零基础 ...
- 14西安区域赛总结帖
14西安区域赛总结帖 ACM 第一次打区域赛,终于可以好好地写一篇总结帖了. 总结帖=口水帖?差不多吧.(在我离开的时候发生了些事情,我现在心情不是很好,我就随便水水吧) 文章结构就用总分总吧. ...
- Java算法:牛客网腾讯笔试真题算法Java版1-11题
题号 题目 知识点 难度 通过率 QQ1 生成格雷码 递归 简单 22.61%QQ2 微信红包 模拟 简单 25.61%QQ3 编码 字符串模拟 中等 26.60%QQ4 游戏任务标记 模拟 中等 3 ...
- 有点坎坷,却又有点感动。
5.23 UPD:其实最近这段时间虽然打了不少农药,但也写了一些题,感觉就算不打游戏我也来不及写题解.接下来除了期末考试主要还是补容斥和dp方面的东西,以及点分治这个我一直理解不好的东西,应该会写一些 ...
- enspar启动失败40_适合烘焙新手第一次做的芝士面包,简单易上手,好吃松软零失败...
作为一个爱吃的人,家里自然是少不了烤箱哒,入手烤箱也有快3个月了,从没有烤箱的时候看到大家的烘培美食羡慕不已,到有了烤箱后三分钟热乎气烤了红薯,土豆,茄子,再到终于还是尝试入门烘培的饼干,鸡翅等等,一 ...
- 芝士和奶酪一样吗_使用简单工厂设计模式就像制作芝士蛋糕一样
芝士和奶酪一样吗 by Sihui Huang 黄思慧 使用简单工厂设计模式就像制作芝士蛋糕一样 (Using the Simple Factory design pattern is a lot l ...
- 计算几何从入门到入土(题目)
代码中的板子参考了繁凡和俊杰带佬的板子 二维基础 A.计算几何spj hacker 题意: 输入一组数a,b使无论ans为任何数,以下函数都返回AC. // a and b are outputed ...
最新文章
- linux查进程ps和top,Linux中几个进程查看命令总结 ps, top, htop, vmstat
- python如何将图片的像素矩阵绘制成图片(python,matplotlib):TypeError: Invalid shape (1, 28, 28) for image data
- [五] JavaIO之InputStream OutputStream简介 方法列表说明
- 分布式事物解决方案-TCC
- C#String与string大小写的区别
- FTP的主动模式(PORT Mode)及被动模式(Passive Mode)
- Java使用JDBC连接随意类型数据库(mysql oracle。。)
- [蓝桥杯2019初赛]年号字串-数论+模拟
- 蛋白对接_JCIM | 金属蛋白分子对接程序哪家强?七种对接程序的基准测试
- react 改变css样式_web前端入门到实战:编写CSS代码的8个策略,资深开发工程师总结...
- 答网友:如何在Sbo Add-on中激活或者禁止系统增加、查找和导航按钮?
- 网络中最常用的网络命令(5)-完整参数
- 剑指Offer之寻找二叉搜索树的第k个节点
- Java好还是Python好?一张图告诉你!
- Linux的互斥锁、条件锁的用法
- [转]DOS批处理高级教程精选合编
- linux c语言 修改mac地址,C语言根据MAC地址查找网卡并修改IP地址
- 国标视频平台搭建(七)配置https访问
- C语言精练教程:连载中
- 用jQuery实现旋转木马效果(带前后按钮和索引按钮)
热门文章
- android 随音乐旋律,Deemo - 叩击心灵的旋律 - Android 应用 - 音乐游戏 - 【最美应用】...
- 无线路由器当无线交换机用
- python写一个程序可以不不断的输⼊入数字,直到输⼊入的数字是0打印 结束 后程序结束。 正确代码:
- android 新闻应用、Xposed模块、酷炫的加载动画、下载模块、九宫格控件等源码
- 2022年海南最新建筑八大员(机械员)模拟考试题库及答案
- apple邀请码发放(apple邀请码,apple卡密获取)
- JAVA经验:很有启发(四)
- xml布局html 实例,布局xml文件和模板phtml的对应关系
- 瞬间把自己家里的ipad或华为平板、手机变成电脑副屏
- 从互联网+角度看云计算的现状与未来(1)