成为计算几何master之路——记算法竞赛中常用的计几算法及思想

本文详细介绍了计算几何在算法竞赛中的应用,包括精度处理、剖分策略和层次化设计思想。从点、向量、线、圆、简单多边形和数值计算等多个方面阐述经典算法和技巧,如点在线段上的判定、线线交求交点、三角剖分等。同时,讨论了插值法和数值积分在处理复杂几何问题时的作用。
摘要由CSDN通过智能技术生成

成为计算几何MASTER(FAKE)之路

1 引言

计算几何计算机科学的一个重要分支,因此在算法竞赛中也是常考的一类题,难度从签到题到防AK题不等。本文是作者对计算几何在算法竞赛中的解题学应用的一点心得,主要介绍计算几何专题内比较经典的思想,算法和个人对此的一点心得。本文从逻辑上分为三个部分,第一部分是阐释解决有关计算几何算法问题时的设计思想,第二部分从点,向量,圆,三角,简单多边形等计算几何中主要处理的二维图像的角度出发,用面向对象的思想介绍类成员函数和成员变量(但是出于程序实现的方便,在设计程序时依然以面向过程为主),主要采用的手段仍然以解析几何为主。最后介绍非解析方法的数值计算技巧,用以解决一类其他的问题。本文将围绕问题转化,分类讨论等算法设计中常用的思想对上述内容进行阐释。

1.1 精度

在以解析几何为理论背景的计算几何问题中,精度对程序正确性的影响非常大,其中尤其以开根操作和三角函数操作影响恶劣。此外,受限于计算机存储空间有限性,在逻辑上无法直接存储无尽小数(不考虑分数类等间接表示的方法),所以在经过一系列操作后==操作符可能无法判断逻辑上等价而数值上不等价的表达式。

关于精度,主要就是要解决上述两个问题:精度降低和因此带来的等价判断处理。

第一个问题目前还没有很好的办法解决,在部分不需要非线性运算的问题中,可以用分数类来实现逻辑上的精确表述,在输出结果前不会产生精度损失。但是当遇到开根,三角函数等精度杀手时,分数类就显得力不从心了。一般而言,当精度要求为 1 0 − 6 10^{-6} 106 时,可以容忍一次到二次开根操作或者一次(反)三角函数运算。使用long double可以略微提升精度,但是效果不明显。

在等价判断上,一般设一个所需精度级别的误差量,当两个数之差小于该误差量时,可以认为这两个数相等。因为这最多带来小于所需精度级别的误差,基本可以认为他是安全的。当该操作后还要套很多操作时,可以适当减小这个误差量。使用一个cmp函数来实现比较功能,返回值类似java中的 compareTo函数。

const double EPS=1e-6;
int cmp(const double &a,const double &b){
   
	if(fabs(a-b)<EPS)return 0;
	return a>b?1:-1;
}

另一个常用的手段是用long long存储数据。在处理不涉及距离,面积的问题时(或者只在最后一步求),如凸包(只需要处理叉积),可以判断性的操作都在整数范围下完成,只在计算距离面积数值时才转换为浮点型计算。这样可以有效避免各种浮点误差。

1.2 剖分

在非算法竞赛中说的三角剖分,常指在一个简单多边形的顶点间连若干条互不相交的线段,将之分解成若干个三角形,从而对于多边形的面积,重心,面积交等问题时可以通过这些三角形间接求出来。这本质上是一种转换的思想,将不好处理的多边形,转换为熟悉的三角形,在三角形上进行分类讨论来解决各类实际问题。

但是这种做法在实现时非常复杂,先要用扫描线法进行单调多边形的划分,然后再在单调多边形上用扫描线法求出三角剖分,编程复杂度巨大。

考虑一个更加简便的做法,在求面积的情况下,本质上是对三角形面积的加和。当三角剖分没有相交时,出现的所有三角形都对结果贡献了正面积。在这里我们考虑负面积,对于一个枢轴点 O O O , 有多边形 P P P 面积为 ∑ i ∈ P o p i ⃗ × o p i + 1 ⃗ \sum_{i\in P}\vec{op_i}\times\vec{op_{i+1}} iPopi ×opi+1 。当叉乘结果为负时,则对结果贡献负面积,最终结果和不相交的三角剖分一致。这样,在处理和面积相关的问题时,对正负面积分别累计,就可以得到一个更加高效的三角剖分的解法。

1.3 层次化设计

在设计程序时,建议对处理的对象进行逐级的定义和初始化,因为计算几何问题往往有着很强的层次性,如多边形在进行三角剖分后处理时,往往需要调用线段之间的操作,而此操作又依赖于点和向量的操作。从简单的几何结构及其操作开始定义,逐步搭建更高级的结构,可以有效降低编程过程中的复杂性。

2 点,向量和线

二维平面上的点和向量都可以用一个二元组来表示,事实上点坐标可以视为一个原点上引出的向量,所以可以将点和向量设计为同一种结构。

#define LL long long
struct point {
   
    LL x,y;
    point operator+(const point &obj)const{
   
        return {
   x+obj.x,y+obj.y};
    }
    point operator-(const point &obj)const{
   
        return {
   x-obj.x,y-obj.y};
    }
    double norm(){
   
		return sqrt(x*x+y*y+0.0);
	}
	LL norm2(){
   
		return x*x+y*y;
	}
};

double dis(const point &a,const point &b){
   
	return (a-b).norm();
}

2.1 点积和叉积

在计算几何中,向量的点积和叉积是有效的判断方向的手段,在只需要定性而不需要定量的向量朝向分析时,点积和叉积可以胜任绝大多数求角度/求斜率操作能求解的问题。
图2.1.1
使用叉积可以判断给定向量在原向量基础上左偏还是右偏,使用点积可以判断给定向量和原向量正向还是反向。不难发现,点积满足交换率,而叉积不满足交换律。

规定 × ( d e t ) \times (det) ×(det) 表示叉积 ⋅ ( d o t ) · (dot) (dot) 表示点积,有二维语境下的定义如下:

a ⃗ × b ⃗ = a . x ∗ b . y − a . y ∗ b . x = ∣ a ∣ ∗ ∣ b ∣ ∗ s i n < a ⃗ , b ⃗ > \vec{a}\times\vec{b} = a.x * b.y - a.y *b.x = |a|*|b|*sin<\vec{a},\vec{b}> a ×b =a.xb.ya.yb.x=absin<a ,b >

a ⃗ ⋅ b ⃗ = a . x ∗ b . x + a . y ∗ b . y = ∣ a ∣ ∗ ∣ b ∣ ∗ c o s < a ⃗ , b ⃗ > \vec{a} · \vec{b} = a.x * b.x + a.y *b.y = |a|*|b|*cos<\vec{a},\vec{b}> a b =a.xb.x+a.yb.y=abcos<a ,b >

double det(const point &a,const point &b){
   
    return a.x*b.y-a.y*b.x;
}

double det(const point &o,const point &a,const point &b){
   
    return det(a-o,b-o);
}

double dot(const point &a,const point &b){
   
    return a.x*b.x+a.y*b.y;
}

double dot(const point &o,const point &a,const point &b){
   
    return dot(a-o,b-o);
}

此外,不难发现叉积的绝对值同时是两向量构成的四边形的面积,所以可以通过叉积快速求出三角形面积。

double areaOfTriangle(const point &a,const point &b,const point &c){
   
    return fabs(det(a,b,c)/2);
}

2.2 线段(直线)

线段主要有四种储存方式,两点式,点向式,一般式,斜率式。

一般都以两点式存储,因为其不受斜率限制,可以表示任意一条直线,并且可以表示线段的范围,优势比较明显,此外还可以表示方向。

struct segment{
   
	point s,t;
};

但是在计算方程时,斜率式和一般式更为常用,尤其联立解一次以上方程时,常用斜率式,此时需要注意确认是否是铅直线,以防出现除0RE

对于两点式 l ( s , t ) l(s,t) l(s,t),若 s . x = = t . x s.x==t.x s.x==t.x 则为铅直线,需要另行讨论;否则有:

k = ( s . y − t . y ) ( s . x − t . x ) k=\frac{(s.y-t.y)}{(s.x-t.x)} k=(s.xt.x)(s.yt.y)

b = s . y − k ∗ s . x b=s.y-k*s.x b=s.yks.x

如此便可以从两点式转换为斜率式,反过来处理只需代入端点计算即可。至于两点式和点向式的转换,斜率式和一般式的转换,都较为简单,此处不表。一般而言,我们所说的直线均默认以两点式存储。

2.2.1 点在线段上判定

点在线段上等价于

  • 点在对应直线上
  • 点的横纵坐标在对应范围内

一般而言,在处理问题时,如图形交点,常用直线先求出所有交点,再判断是否在所求线段上,所以第一条条件一般总是满足。大多数情况下只要快速判断第二条即可:

bool isPointOnSegment(const point &o,const segment &l){
   
	double mix=min(l.s.x,l.t.x);
	double mxx=max(l.s.x,l.t.x);
	double miy=min(l.s.y,l.t.y);
	double mxy=max(l.s.y,l.t.y);
    return mix<=o.x&&o.x<=mxx&&miy<=o.y&&o.y<=mxy;
}
2.2.2 线线交求交点

如果是直线,直接转为一般式求解即可。如果是线段,只要在此基础上加上点在线段上判定即可。需要说明的是有必要特判斜率不存在的特殊情况。一个玄学的处理方法是开始对所有点旋转一个特定角度以卡掉铅直线,避免讨论。

2.2.3 线线交判定

此处判定特指线段交,因为在欧氏二维空间中,直线不平行必定相交。可以用上述方法进行大讨论,也可以采用快速排斥+跨立实验的方法。

快速排斥实验指:判断两线段所在平行于坐标轴的矩形是否相交。

跨立实验指是指:判断对任意一条线段,另一线段两端点是否在其两侧。

需要说明的是,如果不能通过跨立实验,说明那么必定不可能相交;如果通过夸跨立实验而不通过快速排斥实验,则说明两条直线共线且有交点。

该判定方法有较多文字资料,可以自行查阅。

2.2.4 点线距

点到直线距离有一般式公式:
d = ∣ A x + B y + C ∣ x 2 + y 2 d=\frac{|Ax+By+C|}{\sqrt{x^2+y^2}} d=x2+y2 Ax+By+C

但是这种写法需要对两点式进行变形,较为麻烦,一般采用面积除以底的形式,利用叉积的性质可以直接得到点 O O O 到线段 A , B A,B A,B 的距离:
d = O A ⃗ × O B ⃗ ∣ A B ⃗ ∣ d=\frac{\vec{OA}\times\vec{OB}}{|\vec{AB}|} d=AB OA ×OB

double dis(const point &o,const segment &l){
   
    return fabs(det(o,l.s,l.t)/dis(l.s,l.t));
}

3 圆和三角函数

圆往往和角度有关,所以在本节中,将圆和三角函数放在一起进行讨论。

圆心和半径可以唯一确定一个圆,因此给出圆的定义。

struct circle{
   
    point cn;
    double r;
};

为了方便起见,在本节中,如无特殊说明,所有的角度均为弧度制。

3.1 正弦定理和余弦定理

在诸如X点共圆的题目中,求圆心角是一个常见操作。圆心角的一个更一般的表述是,对于给定一点引出的两条向量,求他们之间的夹角(即三角形内角)。
图3.1.1
用余弦定理可以容易的得到三角函数值:
cos ⁡ θ = a 2 + b 2 − c 2 2 a b \cos\theta=\frac{a^2+b^2-c^2}{2ab} cosθ=

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值