数论六之计算几何干货——计算几何模板解释全集 及 模板检验训练场

点和向量及运算

:用坐标表示, P ( x , y ) P(x,y) P(x,y)

向量:有大小,方向,与二维点坐标一一对应, P = ( x , y ) P=(x,y) P=(x,y)

向量的运算

定义向量 A = ( x 1 , y 1 ) , B = ( x 2 , y 2 ) A=(x_1,y_1),B=(x_2,y_2) A=(x1,y1),B=(x2,y2)

  • 加减: A ± B = ( x 1 ± x 2 , y 1 ± y 2 ) A±B=(x_1±x_2,y1±y_2) A±B=(x1±x2,y1±y2)

  • 数乘: k A = ( k x 1 , k y 1 ) kA=(kx_1,ky_1) kA=(kx1,ky1)

  • 模长: ∣ A ∣ = x 1 2 + y 1 2 |A|=\sqrt{x_1^2+y_1^2} A=x12+y12

  • 角度: atan2 ⁡ ( y 1 , x 1 ) \operatorname{atan2}(y_1,x_1) atan2(y1,x1)(返回的是弧度制)

    tan ⁡ θ = y x → atan2 ⁡ ( y , x ) = θ \tan\theta=\frac{y}{x}\rightarrow \operatorname{atan2}(y,x)=\theta tanθ=xyatan2(y,x)=θ

  • 点积/内积( ⋅ · ): A ⋅ B = x 1 x 2 + y 1 y 2 A·B=x_1x_2+y_1y_2 AB=x1x2+y1y2

    几何意义: A A A乘以 B B B A A A上的投影, A ⋅ B = ∣ A ∣ ∣ B ∣ cos ⁡ θ A·B=|A||B|\cos\theta AB=ABcosθ

    A ⋅ B = 0 A·B=0 AB=0则两向量垂直; > 0 >0 >0夹角为锐角; < 0 <0 <0为钝角

    在这里插入图片描述

  • 叉积/外积( × \times ×): A × B = ∣ x 1 y 1 x 2 y 2 ∣ = x 1 y 2 − x 2 y 1 = − B × A A\times B=\begin{vmatrix}x_1&y_1\\x_2&y_2\end{vmatrix}=x_1y_2-x_2y_1=-B\times A A×B=x1x2y1y2=x1y2x2y1=B×A

    与行列式的几何意义联系:都表示 x o y xoy xoy二维平面上以 A , B A,B A,B为邻边的平行四边形的有向面积 A × B = ∣ A ∣ ∣ B ∣ sin ⁡ θ A\times B=|A||B|\sin\theta A×B=ABsinθ

    A × B = 0 A\times B=0 A×B=0,则向量 A , B A,B A,B共线; > 0 >0 >0说明从 B B B转到 A A A是顺时针; < 0 <0 <0则从 B B B转到 A A A是逆时针

    在这里插入图片描述

  • 旋转:把 A A A逆时针旋转 α \alpha α弧度得到新向量 A ′ A' A
    A = ( x 1 , y 1 ) = r ( cos ⁡ θ , sin ⁡ θ ) ⇒ A ′ = r ( cos ⁡ ( θ + α ) , sin ⁡ ( θ + α ) ) A=(x_1,y_1)=r(\cos\theta,\sin\theta)\Rightarrow A'=r\Big(\cos(\theta+\alpha),\sin(\theta+\alpha)\Big) A=(x1,y1)=r(cosθ,sinθ)A=r(cos(θ+α),sin(θ+α)) = r ( cos ⁡ θ cos ⁡ α − sin ⁡ θ sin ⁡ α , cos ⁡ θ sin ⁡ α + sin ⁡ θ cos ⁡ α ) =r(\cos\theta\cos\alpha-\sin\theta\sin\alpha,\cos\theta\sin\alpha+\sin\theta\cos\alpha) =r(cosθcosαsinθsinα,cosθsinα+sinθcosα) = ( r cos ⁡ θ cos ⁡ α − r sin ⁡ θ sin ⁡ α , r cos ⁡ θ sin ⁡ α + r sin ⁡ θ cos ⁡ α ) =(r\cos\theta\cos\alpha-r\sin\theta\sin\alpha,r\cos\theta\sin\alpha+r\sin\theta\cos\alpha) =(rcosθcosαrsinθsinα,rcosθsinα+rsinθcosα) = ( x 1 cos ⁡ α − y 1 sin ⁡ α , x 1 sin ⁡ α + y 1 cos ⁡ α ) =(x_1\cos\alpha-y_1\sin\alpha,x_1\sin\alpha+y_1\cos\alpha) =(x1cosαy1sinα,x1sinα+y1cosα)

acos ⁡ \operatorname{acos} acos接受 [ − 1 , 1 ] [-1,1] [1,1]的浮点数,输出的范围是 [ 0 , π ] [0,\pi] [0,π]
asin ⁡ \operatorname{asin} asin接受 [ − 1 , 1 ] [-1,1] [1,1]的浮点数,输出的范围是 [ − π 2 , π 2 ] [-\frac{\pi}{2},\frac{\pi}{2}] [2π,2π]
atan ⁡ \operatorname{atan} atan接受浮点数,输出的范围是 [ − π 2 , π 2 ] [-\frac{\pi}{2},\frac{\pi}{2}] [2π,2π]
atan2 ⁡ \operatorname{atan2} atan2接受两个浮点数 y , x y,x y,x(坐标),输出的范围是 [ − π , π ] [-\pi,\pi] [π,π]
点在第一象限 [ 0 , π 2 ] [0,\frac{\pi}{2}] [0,2π],第二象限 [ π 2 , π ] [\frac{\pi}{2},\pi] [2π,π],第三象限 [ − π , − π 2 ] [-\pi,-\frac{\pi}{2}] [π,2π],第四象限 [ − π 2 , 0 ] [-\frac{\pi}{2},0] [2π,0]

struct vec {
	double x, y;
	vec(){}
	vec( double X, double Y ) { x = X, y = Y; }
	vec operator + ( vec t ) { return vec( x + t.x, y + t.y ); }
	vec operator - ( vec t ) { return vec( x - t.x, y - t.y ); }
	vec operator * ( double t ) { return vec( x * t, y * t ); }
	double dot( vec t ) { return x * t.x + y * t.y; }
	friend double dot( vec s, vec t ) { return s.x * t.x + s.y * t.y; }
	double cross( vec t ) { return x * t.y - y * t.x; }
	friend double cross( vec s, vec t ) { return s.x * t.y - s.y * t.x; }
	double angle() { return atan2( y, x ); }
	friend double angle( vec t ) { return atan2( t.y, t.x ); }
	vec rotate( double rad ) { return vec( x * cos( rad ) - y * sin( rad ), x * sin( rad ) + y * cos( rad ) ); }
	friend vec rotate( vec t, double rad ) { return vec( t.x * cos( rad ) - t.y * sin( rad ), t.x * sin( rad ) + t.y * cos( rad ) ); }
	double len() { return sqrt( dot( *this ) ); }
};
struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	point operator + ( vec p ) { return point( x + p.x, y + p.y ); }
	vec operator - ( point v ) { return vec( x - v.x, y - v.y ); }
};

直线和线段

直线的表示方式

  • 斜率式: k x + b kx+b kx+b

    优点:可以控制 x x x的取值来得到线段或者射线

    缺点:不能表示 x = c x=c x=c的直线,计算斜率 k k k需要除法

  • 一般式: a x + b y + c = 0 ax+by+c=0 ax+by+c=0

    优点:可以表示所有的直线, a , b , c a,b,c a,b,c不是固定的,可以同时扩大缩小

    尽量避免除法以提高精度,方便用解析几何的方法算东西

    缺点:参数会多一个,线段或者射线不好处理

  • 两点式: A , B A,B A,B

    优点:简单

    缺点:没有考虑直线上的点的特点

  • 一个点和直线的方向向量
    设向量 v = B − A v=B-A v=BA,则直线 A B AB AB上的点可以表示为 A + v t A+vt A+vt t t t任取)

    优点:可以表示所有的直线,并且通过限制 t t t的取值,可以得到线段或者射线

    如取 t t t 0 − 1 0-1 01之间,得到线段,取 t > 0 t>0 t>0,得到射线

求解点到直线的距离/点在直线上

  • 用解析几何的方法设直线 l : a x + b y + c = 0 l:ax+by+c=0 l:ax+by+c=0,点 p ( x 0 , y 0 ) p(x_0,y_0) p(x0,y0) l l l的距离为 ∣ a x 0 + b y 0 + c ∣ a 2 + b 2 \frac{|ax_0+by_0+c|}{\sqrt{a^2+b^2}} a2+b2 ax0+by0+c

  • 用叉积的方法

    用叉积求出以 p 2 p 1 p_2p_1 p2p1 p 2 p 0 p_2p_0 p2p0为邻边的平行四边形的面积,然后除以底 p 1 p 2 p_1p_2 p1p2的长度

    在这里插入图片描述

    同样通过叉积判断点 p p p是否在直线上,如果在那么构成的平行四边形就是一条线面积为 0 0 0

struct line {//直线由一个端点p加上表示方向的向量v表示 
	point p; vec v;
	line(){}
	line( point P, vec V ) { p = P, v = V; };
	friend double dis( line l, point p ) {//点p到直线l的距离
		vec v = p - l.p;
		double s = cross( l.v, v );//叉积求出平行四边形的面积 
		return fabs( s ) / l.v.len();//面积除以底边的长度就是高(距离) 
	}
	friend bool online( line l, point p ) {//判断点p是否在直线l上 => 共线叉积面积为0 
		vec v = p - l.p;
		double s = cross( l.v, v );
		return dcmp( s ) == 0;
	}
};

求解点到线段的距离/点在线段上

  • 做垂线,垂足落在线段上,那么点到线段的距离就是点到线段所在直线的距离

  • 垂足不落在线段上,此时点到线段的距离就是点到线段的两个端点的距离中较小的那个

在这里插入图片描述

∠ p 0 p 2 p 1 \angle p_0p_2p_1 p0p2p1是钝角且 ∠ p 0 p 1 p 2 \angle p_0p_1p_2 p0p1p2是锐角,则 p 0 p_0 p0到线段 p 1 p + 2 p_1p+2 p1p+2的距离是线段 p 0 p 2 p_0p_2 p0p2的长度

用点积可以求得夹角是否为钝角

在这里插入图片描述

∠ p 0 p 1 p 2 \angle p_0p_1p_2 p0p1p2是钝角且 ∠ p 0 p 2 p 1 \angle p_0p_2p_1 p0p2p1是锐角,则 p 0 p_0 p0到线段 p 1 p 2 p_1p_2 p1p2的距离是线段 p 0 p 1 p_0p_1 p0p1的长度

所以必须两个夹角都是锐角才能说明垂足在线段上

double dis( point p1, point p2, point p ) {//点p到线段(p1,p2)的距离 
	double d1 = dot( p - p1, p2 - p1 ), d2 = dot( p - p2, p1 - p2 );
	if( dcmp( d1 ) >= 0 && dcmp( d2 ) >= 0 ) {
		line l( p1, p2 - p1 );
		return dis( l, p );
	}
	else if( dcmp( d1 ) < 0 ) return len( p - p1 );
	else return len( p - p2 );
}

判断点 p p p是否在线段上,首先要保证 p p p在线段所在直线上,然后出现在两个端点中间

用点积判断,在线段中间则与两端点形成的向量应该是反向的,点积结果应为负

bool onseg( point p1, point p2, point p ) {//判断点p是否在由点p1和点p2构成的线段上 
	line l( p1, p2 - p1 );
	if( ! online( l, p ) ) return 0;//首先要先能在ab直线上
	//在线段ab中间->与两端点形成的向量应该是反向的->点积为负 
	if( dcmp( dot( p - p1, p - p2 ) ) <= 0 ) return 1;
	else return 0; 
}

求解两条线段是否相交

在这里插入图片描述

跨立实验

判断 p 1 , p 2 p_1,p_2 p1,p2是否在直线 p 3 p 4 p_3p_4 p3p4的两侧;再判断 p 3 , p 4 p3,p4 p3,p4是否在直线 p 1 p 2 p_1p_2 p1p2的两侧

跨立实验通过叉积求其正负判断出左边还是右边

两点分别引出与直线端点的两向量的叉积应为非负数

可能出现某条线段端点在另一条线段上,这种情况下的叉积是 0 0 0

在这里插入图片描述

但是也要排除两条线段共线不交的叉积为 0 0 0的情况

bool seg_intersect( point p1, point p2, point p3, point p4 ) {
//判断线段(p1,p2)和(p3,p4)是否有交->跨立实验->一线段两端点分居另一线段两方
	double s1 = cross( p2 - p1, p3 - p1 ), s2 = cross( p2 - p1, p4 - p1 );
	double s3 = cross( p4 - p3, p1 - p3 ), s4 = cross( p4 - p3, p2 - p3 );
	int ret1 = dcmp( s1 ) * dcmp( s2 ), ret2 = dcmp( s3 ) * dcmp( s4 );
	if( ret1 > 0 || ret2 > 0 ) return 0;
	if( ret1 == 0 && ret2 == 0 ) {
		if( onseg( p1, p2, p3 ) || onseg( p1, p2, p4 ) ) return 1;
        //可能交点是两线段的某个端点 
		else return 0;//共线不交
	}
	else return 1;	
}

求解两直线的交点

定义两直线 l : p 1 + t ( p 2 − p 1 ) r : p 3 + s ( p 4 − p 3 ) l:p_1+t(p_2-p_1)\qquad r:p_3+s(p_4-p_3) l:p1+t(p2p1)r:p3+s(p4p3)

则交点可列等式 p 1 + t ( p 2 − p 1 ) = p 3 + s ( p 4 − p 3 ) p_1+t(p_2-p_1)=p_3+s(p_4-p_3) p1+t(p2p1)=p3+s(p4p3)
⇒ t ( p 2 − p 1 ) − s ( p 4 − p 3 ) = p 3 − p 1 ⇒ { x : t ( x 2 − x 1 ) − s ( x 4 − x 3 ) = x 3 − x 1 y : t ( y 2 − y 1 ) − s ( y 4 − y 3 ) = y 3 − y 1 \Rightarrow t(p_2-p_1)-s(p_4-p_3)=p_3-p_1\\ \Rightarrow \begin{cases} x:&t(x_2-x_1)-s(x_4-x_3)=x_3-x_1\\ y:&t(y_2-y_1)-s(y_4-y_3)=y_3-y_1\\ \end{cases} t(p2p1)s(p4p3)=p3p1{x:y:t(x2x1)s(x4x3)=x3x1t(y2y1)s(y4y3)=y3y1

D 0 = ∣ x 2 − x 1 − ( x 4 − x 3 ) y 2 − y 1 − ( y 4 − y 3 ) ∣ = − ∣ x 2 − x 1 x 4 − x 3 y 2 − y 1 y 4 − y 3 ∣ D_0= \begin{vmatrix} x_2-x_1&-(x_4-x_3)\\ y_2-y_1&-(y_4-y_3)\\ \end{vmatrix}=- \begin{vmatrix} x_2-x_1&x_4-x_3\\ y_2-y_1&y_4-y_3\\ \end{vmatrix} D0=x2x1y2y1(x4x3)(y4y3)=x2x1y2y1x4x3y4y3

D t = ∣ x 3 − x 1 − ( x 4 − x 3 ) y 3 − y 1 − ( y 4 − y 3 ) ∣ = ∣ x 1 − x 3 x 4 − x 3 y 1 − y 3 y 4 − y 3 ∣ D_t= \begin{vmatrix} x_3-x_1&-(x_4-x_3)\\ y_3-y_1&-(y_4-y_3)\\ \end{vmatrix}= \begin{vmatrix} x_1-x_3&x_4-x_3\\ y_1-y_3&y_4-y_3\\ \end{vmatrix} Dt=x3x1y3y1(x4x3)(y4y3)=x1x3y1y3x4x3y4y3

D s = ∣ x 2 − x 1 x 3 − x 1 y 2 − y 1 y 3 − y 1 ∣ = − ∣ x 2 − x 1 x 1 − x 3 y 2 − y 1 y 1 − y 3 ∣ D_s= \begin{vmatrix} x_2-x_1&x_3-x_1\\ y_2-y_1&y_3-y_1\\ \end{vmatrix}=- \begin{vmatrix} x_2-x_1&x_1-x_3\\ y_2-y_1&y_1-y_3\\ \end{vmatrix} Ds=x2x1y2y1x3x1y3y1=x2x1y2y1x1x3y1y3

u ⃗ = p 2 − p 1 = ( x 2 − x 1 , y 2 − y 1 ) v ⃗ = p 4 − p 3 = ( x 4 − x 3 , y 4 − y 3 ) w ⃗ = p 1 − p 3 = ( x 1 − x 3 , y 1 − y 3 ) \vec{u}=p_2-p_1=(x_2-x_1,y_2-y_1)\\ \vec{v}=p_4-p_3=(x_4-x_3,y_4-y_3)\\ \vec{w}=p_1-p_3=(x_1-x_3,y_1-y_3) u =p2p1=(x2x1,y2y1)v =p4p3=(x4x3,y4y3)w =p1p3=(x1x3,y1y3)

{ D 0 = − u ⃗ × v ⃗ D s = − u ⃗ × w ⃗ D t = w ⃗ × v ⃗ \begin{cases} D_0=-\vec{u}\times\vec{v}\\ D_s=-\vec{u}\times\vec{w}\\ D_t=\vec{w}\times\vec{v}\\ \end{cases} D0=u ×v Ds=u ×w Dt=w ×v

t = D t D 0 = v ⃗ × w ⃗ u ⃗ × v ⃗ s = D s D 0 = u ⃗ × w ⃗ u ⃗ × v ⃗ t=\frac{D_t}{D_0}=\frac{\vec{v}\times\vec{w}}{\vec{u}\times\vec{v}}\\ s=\frac{D_s}{D_0}=\frac{\vec{u}\times\vec{w}}{\vec{u}\times\vec{v}}\\ t=D0Dt=u ×v v ×w s=D0Ds=u ×v u ×w

注意当且仅当 D 0 = 0 D_0=0 D0=0时,方程可能有多解(直线重合)/ 可能无解(直线平行)

否则可以根据 t t t s s s的取值判断交点是否在线段上

point line_intersect( line l, line r ) {//求交点
	vec v = l.p - r.p;//\vec{w}
	double s = cross( l.v, r.v );//l.v->\vec{u};r.v->\vec{v}
	if( dcmp( s ) != 0 ) {//两直线不重合
	 	double t = cross( r.v, v ) / s;
	 	return l.p + l.v * t;
	}
	else return l.p;
}

多边形

用多边形的顶点集合表示(顶点顺序不同,对应的多边形可能会不同

在这里插入图片描述在这里插入图片描述

也可以按极角的规律顺序

求解多边形面积

  • 凸多边形

    在这里插入图片描述

    对凸多边形做三角剖分,然后求每个三角形的面积

    三角形面积:两个向量的叉积表示夹成的平行四边形的面积,除以 2 2 2就是三角形的面

  • 凹多边形

    • 计算出 △ A B C \triangle ABC ABC的面积

      在这里插入图片描述

    • 计算出凹四边形 A B C D ABCD ABCD的面积(实际上是 △ A C D − △ A B C \triangle ACD-\triangle ABC ACDABC

      在这里插入图片描述

    • 计算出复杂五边形 A B C D E ABCDE ABCDE的面积(实际上是四边形 A B C D − △ A D E ABCD-\triangle ADE ABCDADE

      在这里插入图片描述

      P S PS PS:这个五边形不是简单多边形,简单多边形是由边不交的线段组成的多边形,这里 A E AE AE B C BC BC相交了,不妨设交点是 G G G

      那么这里算的面积实际上是凹四边形 G C D E GCDE GCDE的面积减去 △ A G B \triangle AGB AGB的面积

    • 计算出凹六边形 A B C D E F ABCDEF ABCDEF的面积(实际上是五边形 A B C D E + △ A E F ABCDE+\triangle AEF ABCDE+AEF

      在这里插入图片描述

    实际上,这种剖分方法已经不再适用于凹多边形了

其实只需要搞清楚什么时候加三角形面积什么时候减三角形面积

(发现 A C AC AC A B AB AB右边所以减, A D AD AD A C AC AC左边所以加 . . . ... ...

用叉积算出有向面积求和即可,简单多边形的面积就是多个三角形的有向面积和

甚至可以不用多边形上的顶点,随便选一个点进行三角剖分都可以

在这里插入图片描述

double area( vector < point > polygon ) {//计算多边形面积 多边形由所有端点集合表示
	double s = 0; int n = polygon.size();
	for( int i = 0;i < n;i ++ )
		s += ( polygon[i] - point( 0, 0 ) ).cross( polygon[( i + 1 ) % n] - point( 0, 0 ) );
	return fabs( s / 2 ); 
}

求解多边形重心

重心公式 ( x 1 + x 2 + x 3 3 , y 1 + y 2 + y 3 3 ) (\frac{x_1+x_2+x_3}{3},\frac{y_1+y_2+y_3}{3}) (3x1+x2+x3,3y1+y2+y3)

多边形的重心并不是顶点坐标平均数,当然如果所有质量都在顶点上那么这个公式就是正确的

但是显然多边形是质量分布均匀的

同样的,把多边形进行三角剖分,把每个三角形的质量放到三角形重心处(大小为三角形有向面积),求加权平均数

point center( vector < point > polygon ) {
	double s = 0; int n = polygon.size(); point p( 0, 0 );
	for( int i = 0;i < n;i ++ ) {
		double t = ( polygon[i] - point( 0, 0 ) ).cross( polygon[( i + 1 ) % n] - point( 0, 0 ) );
		s += t;
		p.x += ( polygon[i].x + polygon[( i + 1 ) % n].x ) * t;
		p.y += ( polygon[i].y + polygon[( i + 1 ) % n].y ) * t;
	}
	p.x /= 3, p.x /= s, p.y /= 3, p.y /= s;
	return p;
}

求解判断定点与多边形的位置关系

点恰好在多边形边上,可以通过枚举多边形的边特判掉;剩下来只需要考虑位置在内外

  • 射线法

    从定点随机射出一条引向无穷远点的射线,根据射线与多边形的交点个数判断位置

    如果交点为偶数,则在多边形外;否则在多边形内

    证明显然:如果是在外面那么射入多边形就必定伴随着射出多边形(多边形是个封闭图形)产生偶数个交点

    在这里插入图片描述在这里插入图片描述

bool inpolygon_line( point p, vector < point > polygon ) {//射线法判断点p是否在多边形内 
  	int n = polygon.size();
  	double r = ( rand() / double( RAND_MAX ) - 0.5 ) * 2 * M_PI;//随机一条射线 
  	vec v( cos( r ), sin( r ) );
  	bool ans = 0;
  	for( int i = 0;i < n;i ++ ) {
  		if( onseg( polygon[i], polygon[( i + 1 ) % n], p ) ) return 1;//特判点在多边形边上
  		if( inangle( polygon[i] - p, polygon[( i + 1 ) % n] - p, v ) ) ans ^= 1;
  	}
  	return ans; 
}
  • 转角法

    沿多边形走一圈,累计绕点旋转了多少角度

    0 0 0:点在多边形外

    ± π ±π ±π:点在多边形上

    ± 2 π ±2π ±2π:点在多边形内

    在这里插入图片描述

    在这里插入图片描述

    缺点:角度计算要用反三角函数,精度不高,速度较慢

    然则在实现中,不可能花费时间精度去真的求角度判定

    非常巧妙得利用多边形定点编号自带顺序分辨定点的位置特点(结合代码观图解)

    在这里插入图片描述

    • I I I,点 L L L

      所有点都在其下方/上方,根据 y y y永远不可能触发d<=0&&d'>0的条件,程序判定正确

    • K K K,点 J J J

      因为多边形自带顶点顺序,满足 c c c条件时, d d d条件恰好与 i f if if语句相反,程序判定正确

    • M M M

      C D → \overrightarrow{CD} CD ,满足 i + 1 i+1 i+1 i i i左边且 i + 1 i+1 i+1在点下方, i i i在点上方,cnt--

      F G → \overrightarrow{FG} FG ,满足 i + 1 i+1 i+1 i i i右边且 i + 1 i+1 i+1在点上方, i i i在点下方,cnt++

      程序判定正确

    关键点在于,因为多边形自带顶点顺序且封闭,所以当点在多边形外的时候,如果点(竖直位置)在一条边的中间,那么必定会有另外一条反向边含有点

    有可能多边形是锯齿图/波浪图,多次触发,也一定是成对的

    bool inpolygon_rad( point p, vector < point > polygon ) {
    	int n = polygon.size(), cnt = 0;
    	for( int i = 0;i < n;i ++ ) {
    		if( onseg( polygon[i], polygon[( i + 1 ) % n], p ) ) return 1;
    		double c = cross( polygon[i] - p, polygon[( i + 1 ) % n] - p );
    		double d1 = polygon[i].y - p.y, d2 = polygon[( i + 1 ) % n].y - p.y;
    		if( c < 0 && d1 <= 0 && d2 > 0 ) cnt ++;
    		if( c > 0 && d2 <= 0 && d1 > 0 ) cnt --;
            //c>0:p与i+1构成的线在p与i构成的线左边(需要p与i的线逆时针旋转)
    	}
    	return cnt != 0;
    }
    
  • 改进的转角法

    把被测点看成坐标原点,对多边形的一条边 P i − P i + 1 P_i-P_{i+1} PiPi+1,有四种情况

    • P i + 1 P_{i+1} Pi+1 P i P_i Pi的相同象限,弧度不变

    • P i + 1 P_{i+1} Pi+1​在 P i P_i Pi​的下一象限,弧度加 π 2 \frac{\pi}{2} 2π

    • P i + 1 P_{i+1} Pi+1 P i P_i Pi的上一象限,弧度减 π 2 \frac{\pi}{2} 2π

    • P i + 1 P_{i+1} Pi+1​​在 P i P_i Pi​​的相对象限,计算向量 O P i → \overrightarrow{OP_i} OPi ​​和 O P i + 1 → \overrightarrow{OP_{i+1}} OPi+1 ​的叉积 M S MS MS

      • M S = 0 MS=0 MS=0​,点在多边形上
      • M S > 0 MS>0 MS>0​​,弧度加 π \pi π
      • M S < 0 MS<0 MS<0​,弧度减 π \pi π

    实现时,需特判点在边上的情况,判断象限的时候 0 0 0​​​​看成正数

    原始转交法的几何原理就是此时的跨象限

    bool inpolygon_Rad( point p, vector < point > polygon ) {
    	int n = polygon.size(), cnt = 0;
    	vector < vec > v;
    	for( int i = 0;i < n;i ++ )
    		v.push_back( polygon[i] - p );
    	for( int i = 0;i < n;i ++ ) {
    		vec v1 = v[i], v2 = v[( i + 1 ) % n ];
    		double c = cross( v1, v2 );
    		if( c == 0 && dot( v1, v2 ) <= 0 ) return 1;
    		int d1 = quadrant( v1 ), d2 = quadrant( v2 );
    		if( d2 == ( d1 + 1 ) % 4 ) cnt ++;
    		if( d2 == ( d1 + 3 ) % 4 ) cnt --;
    		if( d2 == ( d1 + 2 ) % 4 ) {
    			if( c > 0 ) cnt += 2;
    			else if( c < 0 ) cnt -= 2;
    		}
    	}
    	return cnt;
    }
    
  • 对于凸多边形

    还可以利用叉积判断定点逆时针绕多边形转一圈都在边的左边,则定点在多边形内

凸包

定义:将最外层的点连接起来构成的凸多边形,能包含点集中所有的点

形象上理解就是,用橡皮筋撑到无穷大,然后放手,最后橡皮筋蹦到挂住的点围成的多边形

性质

  • 凸包内任意两点构成的线段都被包含于凸包内
  • 凸包是凸多边形
  • 凸包的交仍是凸包
  • n n n个点形成的凸包大小,在完全随机的情况下期望不大

graham扫描法

类似于斜率优化,维护凸壳

  • 先找出算法开始点 p 0 p_0 p0——左下点(先找最下面的点,若有多个最下面点则选择最左的一个点)

  • 然后对每个点进行极角排序(极角相同的距离点近的在前面

    极角:点与极轴的夹角

    极轴:过原点的水平线( x x x​轴)

    在这里插入图片描述

  • 先把 p 0 , p 1 , p 2 p_0,p_1,p_2 p0,p1,p2入栈

    在这里插入图片描述

    把点 C , D , I C,D,I C,D,I入栈

  • 按极角排序枚举点

    • 先枚举 G G G

      在这里插入图片描述

      D − I − G D-I-G DIG发现 I I I左拐到 G G G,把 G G G入栈

      (如果 I I I右拐或者不变方向到 G G G就把 I I I弹栈)

      在左边还是右边的判断,通过叉积可得

    • 然后枚举 F F F

      在这里插入图片描述

      I − G − F I-G-F IGF不转向,将 G G G弹栈; D − I − F D-I-F DIF左转,不弹 I I I,停止;将 F F F入栈

    • 接着枚举 H H H

      I − F − H I-F-H IFH右转,将 F F F弹栈; D − I − H D-I-H DIH左转,保留停止;将 H H H入栈

      在这里插入图片描述

    • 再枚举 E E E

      I − H − E I-H-E IHE左转,将 E E E入栈

      在这里插入图片描述

    • 考虑 A A A

      H − E − A H-E-A HEA右转,弹 E E E I − H − A I-H-A IHA左转,保留停止;入栈 A A A

      在这里插入图片描述

    • 最后是 B B B

      H − A − B H-A-B HAB左转,入栈,整个程序结束

      在这里插入图片描述

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)​,瓶颈在排序

point Po[maxn], sta[maxn], p[maxn];
int graham() {
	for( int i = 0;i < n;i ++ ) p[i] = Po[i];
	int o = 0;
	for( int i = 1;i < n;i ++ )
		if( p[i].y < p[o].y || ( p[i].y == p[o].y && p[i].x < p[o].x ) ) o = i;
	swap( p[o], p[0] );
    //p[0]是起点 不参与排序 所以从p+1开始
	sort( p + 1, p + n, []( point s, point t ) {
		vec v1 = s - p[0], v2 = t - p[0];
		double c = cross( v1, v2 );
		return dcmp( c ) > 0 || ( dcmp( c ) == 0 && dcmp( v1.dot() - v2.dot() ) < 0 );
	} );
	sta[0] = p[0], sta[1] = p[1]; int top = 1;
	for( int i = 2;i < n;i ++ ) {
		while( top && dcmp( cross( sta[top] - sta[top - 1], p[i] - sta[top - 1] ) ) <= 0 ) top --;
		sta[++ top] = p[i];
	}
	return top + 1;
}

缺点:如果要保留凸包边上的点,则刚才的极角排序就不适用了

如果只有在向右拐的时候才弹栈,直走的时候不弹栈,也不能保留凸包边上所有的点

e.g.

在这里插入图片描述

  • A , B , C A,B,C A,B,C​入栈
  • 然后 D D D​入栈
  • 此时遇到 A F , A E AF,AE AF,AE​极角相同,按距离先排 F F F
  • C − D − F C-D-F CDF​左转, F F F​入栈
  • D − F − E D-F-E DFE​右转,结果最后 E E E​​​被弹出栈了

如果规定极角相同离远点小,则 F F F​活 B B B​​亡,总归是顾此失彼

graham扫描法加强版

  • 把对所有点进行的极角排序改造(按 x x x排序)

  • 前两个点 p 0 , p 1 p_0,p_1 p0,p1入栈

  • 枚举后面每个点

    如果栈内元素个数 > 1 >1 >1,设栈顶 t o p top top,栈顶下一个 l s t lst lst

    考虑 l s t − t o p − p i lst-top-p_i lsttoppi是否向左拐

    右拐或者直走都弹栈顶,直到向左拐或只剩下一个栈顶,将 p i p_i pi​入栈

  • 操作后得到点集的下凸壳

  • 把极角排序后的点倒过来做一遍得到点集的上凸壳

e.g.

  • 所有点排序

  • 先把点 1 , 2 1,2 1,2入栈

    在这里插入图片描述

  • 2 2 2弹栈, 3 3 3进栈

    在这里插入图片描述

  • 4 4 4进栈

    在这里插入图片描述

  • 4 4 4出栈, 5 5 5进栈

    在这里插入图片描述

  • 5 5 5出栈, 6 6 6进栈

    在这里插入图片描述

  • 6 6 6出栈, 7 7 7进栈

    在这里插入图片描述

  • 8 8 8进栈

  • 8 8 8出栈, 9 9 9​进栈

  • 9 9 9​​出栈, 10 10 10​​​进栈

  • 11 11 11进栈

  • 11 11 11​​出栈, 12 12 12​​​进栈

  • 12 12 12出栈, 13 13 13进栈

  • 14 14 14进栈

  • 得到下凸壳

    在这里插入图片描述

  • 同理倒序枚举扫描找出上凸壳,凸包刻画成功

    在这里插入图片描述

实际上以 y y y​作为第一关键字排序,先求右半凸包,再求左半凸包也一样

如果叉积为 0 0 0,即不改变方向时不弹栈,就可以保留凸包边上的点

point Po[maxn], p[maxn], sta[maxn];
int GraHam( int n ) {
	for( int i = 0;i < n;i ++ ) p[i] = Po[i];
	sort( p, p + n, []( point s, point t ) { return s.x == t.x ? s.y < t.y : s.x < t.x; } );
	int top = 0;
	for( int i = 0;i < n;i ++ ) {
		while( top > 1 && cross( sta[top - 1] - sta[top - 2], p[i] - sta[top - 2] ) <= 0 )
			top --;
		sta[top ++] = p[i];
	}
	int t = top;
	for( int i = n - 2;~ i;i -- ) {
		while( top > t && cross( sta[top - 1] - sta[top - 2], p[i] - sta[top - 2] ) <= 0 )
			top --;
		sta[top ++] = p[i];
	}
	if( n > 1 ) return top - 1;//倒着求凸包的时候多放了个1点进去
	else return top;
}

更重要的区别在于,加强版是根据坐标排序,而极角排序显然会带来更大的误差

所以还是选择维护上下凸壳的加强版更优(亲身体验:UVA-10256 the great divide

圆的表示:圆心 C C C和半径 r r r

圆的一般方程形式: ( x − x 0 ) 2 + ( y − y 0 ) 2 = r 2 (x-x_0)^2+(y-y_0)^2=r^2 (xx0)2+(yy0)2=r2

已知圆心角表示圆上一点: ( x 0 + r cos ⁡ θ , y 0 + r sin ⁡ θ ) (x_0+r\cos\theta,y_0+r\sin\theta) (x0+rcosθ,y0+rsinθ)

求解圆与直线的交点

判定相交

算出圆心到直线的距离 d d d

  • d = r d=r d=r,一个交点
  • d > r d>r d>r,不相交
  • d < r d<r d<r,两个交点

在这里插入图片描述

求解交点

算出垂足 D D D​(已知直线 A B AB AB可得 A D AD AD直线方向向量,从 A A A点走方向向量长度 d d d

∣ C D ∣ = ∣ B D ∣ = r 2 − d 2 |CD|=|BD|=\sqrt{r^2-d^2} CD=BD=r2d2

利用 B C BC BC直线的方向向量同理找到所有交点坐标

求解圆与圆的交点

判定相交:圆心距 d ≤ r 1 + r 2 d\le r_1+r_2 dr1+r2

在这里插入图片描述

求解交点

  • 联立方程求解

  • 几何法

    已知 ∣ A C ∣ , ∣ A B ∣ , ∣ B C ∣ |AC|,|AB|,|BC| AC,AB,BC余弦定理可求 ∠ B C A = α \angle BCA=\alpha BCA=α

    C A CA CA​直线已知,可求得 C A CA CA​直线的单位方向向量 c a → \overrightarrow{ca} ca

    c a ⃗ \vec{ca} ca 逆时针/顺时针旋转 α \alpha α,从 C C C开始走长度 ∣ B C ∣ |BC| BC的向量

求解定点与定圆的切线

切线数量

  • 点在圆内,无
  • 点在圆上, 1 1 1
  • 点在圆外, 2 2 2

求解切线

  • 数学解析法

    ( x 0 , y 0 ) (x_0,y_0) (x0,y0)​​​在圆 ( x − a ) 2 − ( y − b ) 2 = r 2 (x-a)^2-(y-b)^2=r^2 (xa)2(yb)2=r2​​​的切线解析式为 ( x 0 − a ) 2 + ( y 0 − b ) 2 = r 2 (x_0-a)^2+(y_0-b)^2=r^2 (x0a)2+(y0b)2=r2​​

  • 几何法

    在这里插入图片描述

    已知 ∣ A C ∣ = d , ∣ A B ∣ = r |AC|=d,|AB|=r AC=d,AB=r,勾股定理求得 ∣ B C ∣ |BC| BC长度

    余弦定理可解 ∠ B C A α \angle BCA\alpha BCAα,又知直线 C A CA CA方向向量

    逆时针/顺时针旋转 α \alpha α C B CB CB直线方向向量,从 C C C出发走长度 ∣ B C ∣ |BC| BC

求解圆公切线

公切线数量判定

  • 重合: d = 0 d=0 d=0,无数条
  • 内含: 0 < d < r 2 − r 1 0<d<r_2-r_1 0<d<r2r1 0 0 0
  • 内切: d = r 2 − r 1 d=r_2-r_1 d=r2r1 1 1 1
  • 相交: r 2 − r 1 < d < r 2 + r 1 r_2-r_1<d<r_2+r_1 r2r1<d<r2+r1 2 2 2
  • 外切: d = r 1 + r 2 d=r_1+r_2 d=r1+r2 1 1 1
  • 外离: r 1 + r 2 < d r_1+r_2<d r1+r2<d 2 2 2条外公切线, 2 2 2条内公切线

求解公切线

  • 重合,内含不用求

  • 内切/外切的一条内/外公切线对应的是过唯一交点的切线

  • 两条公切线

    在这里插入图片描述

    已知 ∣ A C ∣ = d , ∣ A D ∣ = r 1 , ∣ C E ∣ = r 2 |AC|=d,|AD|=r_1,|CE|=r_2 AC=d,AD=r1,CE=r2,垂足为 D , E D,E D,E

    相似求解 F A F C = A D C E = F D F E \frac{FA}{FC}=\frac{AD}{CE}=\frac{FD}{FE} FCFA=CEAD=FEFD ∣ A F ∣ , ∣ F D ∣ , ∣ D E ∣ |AF|,|FD|,|DE| AF,FD,DE长度可得

    余弦定理得 ∠ A F D \angle AFD AFD

    C A CA CA直线已知,从 A A A出发走 c a ⃗ \vec{ca} ca 长度 ∣ A F ∣ |AF| AF得到两外公切线交点 F F F

    逆时针/顺时针旋转相同角度 α \alpha α,走一定长度得到与圆的交点,两点确定一条直线

    (内切线同理)

pick定理

点阵中顶点在格点上的多边形面积公式 2 S = 2 a + b − 2 2S=2a+b-2 2S=2a+b2

其中 a a a表示多边形内部的点数, b b b表示多边形边界上的点数, S S S表示多边形的面积

在这里插入图片描述

一般是反过来使用求 a a a

S S S多边形面积可以用叉积解决

边界上的点数 b b b gcd ⁡ \gcd gcd​解决:求和枚举多边形的边,一边两点的横纵坐标差的最大公约数 + 1 +1 +1

设线段 ( x 1 , y 1 ) − ( x 2 , y 2 ) ⇒ c n t = gcd ⁡ ( ∣ x 2 − x 1 ∣ , ∣ y 2 − y 1 ∣ ) + 1 (x_1,y_1)-(x_2,y_2)\Rightarrow cnt=\gcd(|x_2-x_1|,|y_2-y_1|)+1 (x1,y1)(x2,y2)cnt=gcd(x2x1,y2y1)+1

pick定理扩展:一条线段下方有多少顶点

等价于求 ∑ x = 0 n ⌊ a x + b c ⌋ \sum_{x=0}^n\lfloor\frac{ax+b}{c}\rfloor x=0ncax+b

求解圆相交面积

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
#define eps 1e-10

double pi = acos( -1.0 );

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	friend double dis( point p1, point p2 ) { return sqrt( ( p1.x - p2.x ) * ( p1.x - p2.x ) + ( p1.y - p2.y ) * ( p1.y - p2.y ) ); }
};

struct circle {
	point o; double r;
	circle(){}
	circle( point O, double R ) { o = O, r = R; }
};

double area_circle( circle c1, circle c2 ) {
	double d = dis( c1.o, c2.o );
	if( dcmp( d - c1.r - c2.r ) >= 0 ) return 0; //外切或外离
	if( dcmp( d - fabs( c1.r - c2.r ) ) <= 0 ) { //内切或内含 
		double r = min( c1.r, c2.r );
		return pi * r * r;
	}
	//相交
	double rad1 = acos( ( c1.r * c1.r + d * d - c2.r * c2.r ) / ( 2 * c1.r * d ) ); 
	double rad2 = acos( ( c2.r * c2.r + d * d - c1.r * c1.r ) / ( 2 * c2.r * d ) );
	return rad1 * c1.r * c1.r + rad2 * c2.r * c2.r - c1.r * d * sin( rad1 );
}

欧拉定理(图论)

G G G为任意联通的平面图, n n n表示顶点数, m m m表示边数, r r r表示面数(去除封闭面后剩下的“背景”也算完整的一面)

n − m + r = 2 n-m+r=2 nm+r=2​恒成立

在这里插入图片描述

求解最小圆覆盖

平面上有一些点,用半径最小的圆覆盖这些点,用随机增量法

定义 C i C_i Ci​为覆盖前 i i i​​个点的最小包围圆

  • p i ∈ C i − 1 p_i\in C_i-1 piCi1​,则 C i = C i − 1 C_i=C_{i-1} Ci=Ci1

  • p i ∉ C i − 1 p_i\notin C_{i-1} pi/Ci1​时的处理,需要对 C i C_i Ci​​另外更新,要求 C i C_i Ci​​覆盖点集 { p 1 , p 2 , … , p i } \{p_1,p_2,…,p_i\} {p1,p2,,pi}

    先让 C i = { p 1 , p i } C_i=\{p_1,p_i\} Ci={p1,pi},逐个判断点 { p 2 , p 3 , … , p i − 1 } \{p_2,p_3,…,p_{i-1}\} {p2,p3,,pi1}

    如果存在 p j ∉ C i p_j\notin C_i pj/Ci,则 C i = { p j , p i } C_i=\{p_j,p_i\} Ci={pj,pi}

    同时,再依次判断点 { p 1 , p 2 , … , p j − 1 } \{p_1,p_2,…,p_{j-1}\} {p1,p2,,pj1}​,如果存在 p k ∉ C i p_k\notin C_i pk/Ci ,则 C i = { p k , p j , p i } C_i=\{p_k,p_j,p_i\} Ci={pk,pj,pi}

    因为三点唯一确定一个圆,所以只需在此基础上判断其他的点是否位于圆内,不停地更新 p k p_k pk

    k k k​​​​​的循环完成时,退出循环,更新 p j p_j pj​​​​

    j j j​​​的循环结束时,退出循环,更新 p i p_i pi​​

    i = n i=n i=n时,算法完成

从算法直观上来看,强烈意识感觉这是个假算法

可能会有这样的问题: A B C ABC ABC​​​​三点构成一个圆,现在加入 D D D​​​点,假如 D D D​​不在 ◯ A B C \bigcirc ABC ABC​内

那么按照算法, D D D要在新构造的圆上

  • 先枚举 A A A​​​​, A D AD AD​​​构成圆的直径,假如 B B B​​不在 ◯ A D \bigcirc AD AD​内
  • B D BD BD​​​​构成圆的直径
  • 假如 A A A​​​不在 ◯ B D \bigcirc BD BD​​内,让 A B D ABD ABD​构成一个圆
  • 假如 C C C​​​不在 ◯ A B D \bigcirc ABD ABD​​内,让 C D CD CD​构成圆的直径
  • 假如 A A A​​​不在 ◯ C D \bigcirc CD CD​​内,让 A C D ACD ACD​构成一个圆
  • 假如 B B B​​​不在 ◯ A C D \bigcirc ACD ACD​​内,让 B C D BCD BCD​构成一个圆

这里出现了直觉上的问题:有没有 A A A​不在 ◯ B C D \bigcirc BCD BCD内的可能

事实上是没有的

根据以上描述,平面上四个点 A B C D ABCD ABCD,任意三个点作圆,另外一个点都不在作出的圆内

  • A B C D ABCD ABCD​是凹四边形

    那么对这个凹四边形求凸包,得到三角形

    一定存在一个点在另外三个点构成的三角形内部(或者边上)

    点在三角形内,那么点自然在三角形的外接圆内

  • A B C D ABCD ABCD​​是凸四边形

    假设四点共圆,那么对角和为 18 0 ∘ 180^\circ 180

    如果某个点在此圆外

    由四边形的内角和为 36 0 ∘ 360^\circ 360,一组对角和小于 18 0 ∘ 180^\circ 180,另外一组对角和大于 18 0 ∘ 180^\circ 180

    真正的圆必定是会被枚举到大于此圆的,此时又将某个点包含在圆内了

    所以极限边界就是四点共圆

必然存在一个点在另外三个点形成的圆内

因此算法流程走完后必定求得最小圆覆盖

时间复杂度

i i i个点在包围 ◯ C i \bigcirc C_i Ci上也就 3 3 3个点,所以点 i i i ◯ C i \bigcirc C_i Ci上(不在 ◯ C i − 1 \bigcirc C_{i-1} Ci1内)的概率是很低的,只有 3 i \frac{3}{i} i3

所以对于 i i i,需要枚举 j j j的概率是 3 i \frac{3}{i} i3

对于 j j j,需要枚举k的概率也是 3 j \frac{3}{j} j3

总的复杂度是线性的,当然这是建立在点随机排序的情况下,最坏还是 O ( n 3 ) O(n^3) O(n3)​的

#define eps 1e-12

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	friend double len( point s, point t ) { return sqrt( ( s.x - t.x ) * ( s.x - t.x ) + ( s.y - t.y ) * ( s.y - t.y ) ); }
	point operator + ( point p ) { return point( x + p.x, y + p.y ); }
	point operator / ( double k ) { return point( x / k, y / k ); }
	double operator - ( point p ) { return x * x + y * y - p.x * p.x - p.y * p.y; }
}p[maxn];

double X( point p1, point p2 ) {
	return p1.x - p2.x;
}

double Y( point p1, point p2 ) {
	return p1.y - p2.y;
}

point get_c( point p1, point p2, point p3 ) {
	double t = 2 * ( Y( p2, p1 ) * X( p3, p1 ) - Y( p3, p1 ) * X( p2, p1 ) );
	return point( ( Y( p2, p1 ) * ( p3 - p1 ) - Y( p3, p1 ) * ( p2 - p1 ) ) / t, ( X( p3, p1 ) * ( p2 - p1 ) - X( p2, p1 ) * ( p3 - p1 ) ) / t );
}

int n; point c; double r;
void circle_cover() {
	random_shuffle( p + 1, p + n + 1 );
	c = p[1], r = 0;
	for( int i = 2;i <= n;i ++ )
		if( dcmp( len( c, p[i] ) - r ) > 0 ) {
			c = p[i], r = 0;
			for( int j = 1;j < i;j ++ )
				if( dcmp( len( c, p[j] ) - r ) > 0 ) {
					c = ( p[i] + p[j] ) / 2, r = len( c, p[i] );
					for( int k = 1;k < j;k ++ )
						if( dcmp( len( c, p[k] ) - r ) > 0 )
							c = get_c( p[i], p[j], p[k] ), r = len( c, p[i] );
				}
		}
}

求解最近点对

平面上有n个点,求最近点对

分治

  • 对点按 x x x坐标排序,作一条竖线 x = m i d x=mid x=mid​​​​​​​​​​,把点集分成两半, L L L​​​​​​​​​和 R R R​​

    假设现在已经求出 L L L​​​​​的最近点对距离 d l d_l dl​​​​, R R R​​​的最近点对距离 d r d_r dr​​

    最后的答案要么是 d = min ⁡ ( d l , d r ) d=\min(d_l,d_r) d=min(dl,dr)​,要么是一个 L L L​中的点和一个 R R R​中的点的最短距离

  • 考虑 L L L中的点和 R R R中的点的最短距离如何求

    首先 L L L​中的点, x + d < m i d x+d<mid x+d<mid​的点肯定不行,把可行的点记作 L ′ L' L

    同理 R R R中的点, x > m i d + d x>mid+d x>mid+d的点肯定不行,把可行的点记作 R ′ R' R

    P = L ′ ⋃ R ′ P=L'\bigcup R' P=LR

    • 简单想法就暴力枚举 P P P​中的点对,更新答案,但可能 P P P​中的点数也很多,最坏还是 O ( n 2 ) O(n^2) O(n2)

    • P P P中的点按照 y y y排序

      取定 P P P​​中的点 p i p_i pi​​,枚举到 p j p_j pj​,使得 y p j + 1 − y p i ≥ d y_{p_{j+1}}-y_{p_i}\ge d ypj+1ypid​,就不枚举了

      看上去貌似仍然是 O ( n 2 ) O(n^2) O(n2),事实上并不是,只需要枚举 O ( n ) O(n) O(n)

      因为 L L L​​和 R R R​中的任意两点距离均 ≥ d \ge d d

      所以在重复点去掉的情况下, 对于任意 p i p_i pi,满足 y p j − y p i < d y_{p_{j}}-y_{p_i}< d ypjypi<d​​​​​的点只有常数个

      在这里插入图片描述

闵可夫斯基和(Minkowski Sum)

给定点集 A , B A,B A,B,它们的闵可夫斯基和 A + B A+B A+B被定义为 { a + b ∣ a ∈ A , b ∈ B } \bigg\{a+b\bigg|a\in A,b\in B\bigg\} {a+baA,bB}

可以理解为八其中一个图形沿着另外一个图形的边做平移得到的新图形

在这里插入图片描述

考虑两个凸包的闵可夫斯基和

由于结果也是一个凸包,所以结果集合的这些边也有极角序

并且结果集合上的边都来自于 A , B A,B A,B两个凸包上的边

相当于对 A , B A,B A,B两个凸包上的边根据极角序做归并排序,就是闽可夫斯基和

先对 A , B A,B A,B两个凸包按照极角序排序

一般来说 A , B A,B A,B两个凸包的点都是逆时针顺序的

所以只需要循环位移 A , B A,B A,B两个凸包上的点,线性的把 y y y坐标最小的点移到最前面就可以了

然后在 A , B A,B A,B两个凸包有序的情况下归并得到 A + B A+B A+B

  • 假设现在做到 A i A_i Ai B j B_j Bj两个点,把 A i + B j A_i+B_j Ai+Bj放入集合
  • 用叉积比较 A i + 1 − A i , B j + 1 − B j A_{i+1}-A_i,B_{j+1}-B_j Ai+1Ai,Bj+1Bj​​的极角序,极角序较小的指针 + 1 +1 +1​,如果相同则都 + 1 +1 +1

在这里插入图片描述

可能会有三点共线的情况,只需要丢进去来一遍求凸包即可

vec v1[maxn], v2[maxn];
point PoA[maxn], PoB[maxn];

int minkowski( int n, int m, point *Po ) {
	for( int i = 0;i < n;i ++ )
		v1[i] = PoA[( i + 1 ) % n] - PoA[i];
	for( int i = 0;i < m;i ++ )
		v2[i] = PoB[( i + 1 ) % m] - PoB[i];
	Po[0] = PoA[0] + PoB[0];
	int i = 0, j = 0, cnt = 1;
	while( i < n && j < m ) {
		if( dcmp( cross( v1[i], v2[j] ) ) >= 0 )
			Po[cnt] = Po[cnt - 1] + v1[i ++], cnt ++;
		else
			Po[cnt] = Po[cnt - 1] + v2[j ++], cnt ++;
	}
	while( i < n ) Po[cnt] = Po[cnt - 1] + v1[i ++], cnt ++;
	while( j < m ) Po[cnt] = Po[cnt - 1] + v2[j ++], cnt ++;
	return cnt;
} 

闵可夫斯基和也可以用来求解凸包间的最短距离

  • 在两个凸包内分别任意取两个点 a , b a,b a,b
  • 这两个点作差得到 d → = a − b \overrightarrow{d}=a-b d =ab
    那么由 a , b a,b a,b​​的任意性,对于所有 a , b a,b a,b,对 d → \overrightarrow{d} d ​取最小值就是答案
  • B B B​​凸包的点全部取反(横纵坐标取相反数)得到 B ′ B' B​​
  • 计算闽可夫斯基和 A + B ′ A+B' A+B​,实际上计算的是 A − B A-B AB
  • 然后计算点 ( 0 , 0 ) (0,0) (0,0)​到凸包 A − B A-B AB​的最短距离即可

求定点到凸包的最短距离

  • 枚举凸包的边,求点到线段的距离
  • 参考动态凸包的方法,以凸包内一点为原点进行极角排序,二分找到目标线段求距离

欢迎来到——各大模板训练场!!!

Segments——判断线段相交

problem

如果所有线段在某条直线上的投影有交点,那么从这个交点引出一条垂直于直线的线必定交于所有线段

把垂直的线稍微掰一掰,直到恰好穿过两个端点

所以枚举任意两个端点引出一条直线,判断这直线是否与所有线段相交即可

两个端点可能原来就隶属于统一线段,但一定不能是同一个点

#include <cstdio>
#include <cmath>
using namespace std;
#define eps 1e-8
#define maxn 105

struct point {
	double x, y;
	point() {}
	point( double X, double Y ) { x = X, y = Y; }
	point operator + ( point t ) { return point( x + t.x, y + t.y ); }
	point operator - ( point t ) { return point( x - t.x, y - t.y ); }
	friend double cross( point s, point t ) { return s.x * t.y - s.y * t.x; }
	double dot( point t ) { return x * t.x + y * t.y; }
	double len() { return sqrt( dot( *this ) ); }
};

struct line {
	point s, t;
	line(){}
	line( point S, point T ) { s = S, t = T; }
}Line[maxn];

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

bool intersect( line l, line r ) {
	return dcmp( cross( l.s - r.s, l.t - r.s ) ) * dcmp( cross( l.s - r.t, l.t - r.t ) ) <= 0;
}

int T, n;

bool check( line l ) {
	if( ! dcmp( ( l.t - l.s ).len() ) ) return 0;
	for( int i = 1;i <= n;i ++ )
		if( ! intersect( l, Line[i] ) ) return 0;
	return 1;
}

int main() {
	scanf( "%d", &T );
	again :
	while( T -- ) {
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ ) {
			double lx, ly, rx, ry;
			scanf( "%lf %lf %lf %lf", &lx, &ly, &rx, &ry );
			Line[i] = line( point( lx, ly ), point( rx, ry ) );
		}
		for( int i = 1;i <= n;i ++ )
			for( int j = i;j <= n;j ++ )
				if( check( line( Line[i].s, Line[j].s ) ) || check( line( Line[i].s, Line[j].t ) ) || check( line( Line[i].t, Line[j].s ) ) || check( line( Line[i].t, Line[j].t ) ) ) {
					printf( "Yes!\n" );
					goto again;
				}
		printf( "No!\n" );
	}
	return 0;
}

Points Within——点与多边形位置

problem

模板判断点是否在多边形内部

#include <cmath>
#include <ctime>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define eps 1e-8

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

struct vec {
	double x, y;
	vec(){}
	vec( double X, double Y ) { x = X, y = Y; }
	friend double cross( vec s, vec t ) { return s.x * t.y - s.y * t.x; }
	friend double dot( vec s, vec t ) { return s.x * t.x + s.y * t.y; }
};

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	vec operator - ( point t ) { return vec( x - t.x, y - t.y ); }
};

struct line {
	point p; vec v;
	line(){}
	line( point P, vec V ) { p = P, v = V; }
	friend bool online( line l, point p ) { return dcmp( cross( l.v, p - l.p ) ) == 0; }
};

vector < point > polygon;

bool onseg( point p1, point p2, point p ) {
	line l( p1, p2 - p1 );
	if( ! online( l, p ) ) return 0;
	else if( dcmp( dot( p - p1, p - p2 ) ) <= 0 ) return 1;
	else return 0;
}

bool inangle( vec v1, vec v2, vec v ) {
	if( dcmp( cross( v1, v2 ) ) < 0 ) swap( v1, v2 );
	return cross( v1, v ) >= 0 && cross( v2, v ) <= 0;
}

bool inpolygon( point p ) {
	int n = polygon.size(), ans = 0;
	double r = ( rand() / double( RAND_MAX ) - 0.5 ) * 2 / M_PI;
	vec v( cos( r ), sin( r ) ); 
	for( int i = 0;i < n;i ++ ) {
		if( onseg( polygon[i], polygon[( i + 1 ) % n], p ) ) return 1;
		if( inangle( polygon[i] - p, polygon[( i + 1 ) % n] - p, v ) ) ans ^= 1;
	}
	return ans;
}
int main() {
	int n, m, T = 1;
	while( scanf( "%d", &n ) && n ) {
		scanf( "%d", &m );
		polygon.clear();
		for( int i = 1;i <= n;i ++ ) {
			double x, y;
			scanf( "%lf %lf", &x, &y );
			polygon.push_back( point( x, y ) );
		}
		if( T != 1 ) printf( "\n" );
		printf( "Problem %d:\n", T ), T ++;
		while( m -- ) {
			double x, y;
			scanf( "%lf %lf", &x, &y );
			point p( x, y );
			if( inpolygon( p ) ) printf( "Within\n" );
			else printf( "Outside\n" );
		}
	}
	return 0;
}

Rotational Painting——找重心、求凸包、判断点在线段上

problem

先找到重心,然后求凸包,枚举凸包上的线段,判断重心垂足是否落在线段上

#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 50005
#define eps 1e-10

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

struct vec {
	double x, y;
	vec(){}
	vec( double X, double Y ) { x = X, y = Y; }
	vec operator + ( vec v ) { return vec( x + v.x, y + v.y ); }
	vec operator - ( vec v ) { return vec( x - v.x, y - v.y ); }
	vec operator * ( double k ) { return vec( x * k, y * k ); }
	double dot() { return x * x + y * y; }
	friend double dot( vec u, vec v ) { return u.x * v.x + u.y * v.y; }
	friend double cross( vec u, vec v ) { return u.x * v.y - u.y * v.x; }
};

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	vec operator - ( point p ) { return vec( x - p.x, y - p.y ); };
	point operator + ( vec v ) { return point( x + v.x, y + v.y ); }
	friend double cross( point s, point t ) { return s.x * t.y - s.y * t.x; }
};

struct line {
	point p; vec v;
	line(){}
	line( point P, vec V ) { p = P, v = V; }
};

vector < point > polygon;
int T, n;

point center() {
	double s = 0; point p( 0, 0 );
	for( int i = 0;i < n;i ++ ) {
		double t = cross( polygon[i], polygon[( i + 1 ) % n] );
		s += t;
		p.x += ( polygon[i].x + polygon[( i + 1 ) % n].x ) * t;
		p.y += ( polygon[i].y + polygon[( i + 1 ) % n].y ) * t;
	}
	p.x /= 3, p.x /= s, p.y /= 3, p.y /= s;
	return p;
}

bool check( point p1, point p2, point p ) {
	return dcmp( dot( p - p1, p2 - p1 ) ) > 0 && dcmp( dot( p - p2, p1 - p2 ) ) > 0;
}

point Po[maxn], sta[maxn], p[maxn];

int graham() {
	for( int i = 0;i < n;i ++ ) p[i] = Po[i];
	int o = 0;
	for( int i = 1;i < n;i ++ )
		if( p[i].y < p[o].y || ( p[i].y == p[o].y && p[i].x < p[o].x ) ) o = i;
	swap( p[o], p[0] );
	sort( p + 1, p + n, []( point s, point t ) {
		vec v1 = s - p[0], v2 = t - p[0];
		double c = cross( v1, v2 );
		return dcmp( c ) > 0 || ( dcmp( c ) == 0 && dcmp( v1.dot() - v2.dot() ) < 0 );
	} );
	sta[0] = p[0], sta[1] = p[1]; int top = 1;
	for( int i = 2;i < n;i ++ ) {
		while( top && dcmp( cross( sta[top] - sta[top - 1], p[i] - sta[top - 1] ) ) <= 0 ) top --;
		sta[++ top] = p[i];
	}
	return top + 1;
}

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		polygon.clear();
		for( int i = 0;i < n;i ++ ) {
			double x, y;
			scanf( "%lf %lf", &x, &y );
			Po[i] = point( x, y );
			polygon.push_back( Po[i] );
		}
		point c = center();
		int m = graham(), ans = 0;
		for( int i = 0;i < m;i ++ )
			if( check( sta[i], sta[( i + 1 ) % m], c ) ) ans ++;
		printf( "%d\n", ans );
	}
	return 0;
}

Professor’s task——数据结构(set)维护动态凸包

problem

#include <set>
#include <cmath>
#include <cstdio>
using namespace std;
#define int long long

struct point {
	int x, y, dis;
	double rad;
	point(){}
	point( int X, int Y ) { x = X, y = Y; }
	point operator + ( point p ) { return point( x + p.x, y + p.y ); }
	point operator - ( point p ) { return point( x - p.x, y - p.y ); }
	friend int cross( point s, point t ) { return s.x * t.y - s.y * t.x; }
}Po[5], o;

bool operator < ( const point s, const point t ) { 
	return s.rad < t.rad || ( s.rad == t.rad && s.dis < t.dis );
}

set < point > s;
#define it set < point > :: iterator

it left( it i ) {
	if( i == s.begin() ) i = s.end();
	i --;
	return i;
}

it right( it i ) {
	i ++;
	if( i == s.end() ) i = s.begin();
	return i;
}

int dis( point p ) {
	return ( p.x - o.x ) * ( p.x - o.x ) + ( p.y - o.y ) * ( p.y - o.y );
}

int Q;

signed main() {
	scanf( "%lld", &Q );
	for( int i = 1, x;i <= 3;i ++ ) {
		scanf( "%lld %lld %lld", &x, &Po[i].x, &Po[i].y );
		o = o + Po[i], Po[i].x *= 3, Po[i].y *= 3;
	}
	for( int i = 1;i <= 3;i ++ ) {
		Po[i].dis = dis( Po[i] );
		Po[i].rad = atan2( Po[i].y - o.y, Po[i].x - o.x );
		s.insert( Po[i] );
	}
	Q -= 3;
	int opt, x, y;
	while( Q -- ) {
		scanf ( "%lld %lld %lld", &opt, &x, &y );
		x *= 3, y *= 3;
		point p( x, y );
		p.dis = dis( p );
		p.rad = atan2( y - o.y, x - o.x );
		it r = s.lower_bound( p );
		if( r == s.end() ) r = s.begin();
		it l = left( r );
		if( opt & 1 ) {
			if( cross( p - point(*l), point(*r) - point(*l) ) <= 0 ) continue;
			s.insert( p );
			it now = s.find( p );
			l = left( now ); it lst = left( l );
			while( cross( point(*l) - point(*lst), point(*now) - point(*lst) ) <= 0 )
				s.erase( l ), l = lst, lst = left( lst );
			r = right( now ); it nxt = right( r );
			while( cross( point(*r) - point(*now), point(*nxt) - point(*now) ) <= 0 )
				s.erase( r ), r = nxt, nxt = right( nxt );
		}
		else {
			if( cross( p - point(*l), point(*r) - point(*l) ) <= 0 ) printf( "YES\n" );
			else printf( "NO\n" );
		}
	}
	return 0;
}

The Great Divide——判断凸包是否相交

problem

 #include <bits/stdc++.h>
using namespace std;
#define eps 1e-10
#define maxn 505

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

struct vec {
	double x, y;
	vec(){}
	vec( double X, double Y ) { x = X, y = Y; }
	vec operator + ( vec v ) { return vec( x + v.x, y + v.y ); }
	vec operator - ( vec v ) { return vec( x - v.x, y - v.y ); }
	vec operator * ( double k ) { return vec( x * k, y * k ); }
	double dot() { return x * x + y * y; }
	friend double dot( vec u, vec v ) { return u.x * v.x + u.y * v.y; }
	friend double cross( vec u, vec v ) { return u.x * v.y - u.y * v.x; }
};

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	point operator + ( vec v ) { return point( x + v.x, y + v.y ); }
	vec operator - ( point p ) { return vec( x - p.x, y - p.y ); }
}R[maxn], B[maxn], hallR[maxn], hallB[maxn], p[maxn];

struct line {
	point p; vec v;
	line(){}
	line( point P, vec V ) { p = P, v = V; }
	friend bool online( line l, point p ) { return dcmp( cross( l.v, p - l.p ) ) == 0; }
};

bool onseg( point p1, point p2, point p ) {
	line l( p1, p2 - p1 );
	if( ! online( l, p ) ) return 0;
	if( dcmp( dot( p - p1, p - p2 ) ) <= 0 ) return 1;
	else return 0;
}

bool seg_intersect( point p1, point p2, point p3, point p4 ) {
	double s1 = cross( p2 - p1, p3 - p1 ), s2 = cross( p2 - p1, p4 - p1 );
	double s3 = cross( p4 - p3, p1 - p3 ), s4 = cross( p4 - p3, p2 - p3 );
	int ret1 = dcmp( s1 ) * dcmp( s2 ), ret2 = dcmp( s3 ) * dcmp( s4 );
	if( ret1 > 0 || ret2 > 0 ) return 0;
	else if( ret1 == 0 && ret2 == 0 ) {
		if( onseg( p1, p2, p3 ) || onseg( p1, p2, p4 ) ) return 1;
		else return 0;
	} else return 1;
}

int graham( int n, point *Po, point *sta ) {
	for( int i = 0;i < n;i ++ ) p[i] = Po[i];
	sort( p, p + n, []( point s, point t ) { return s.x == t.x ? s.y < t.y : s.x < t.x; } );
	int top = 0;
	for( int i = 0;i < n;i ++ ) {
		while( top > 1 && cross( sta[top - 1] - sta[top - 2], p[i] - sta[top - 1] ) <= 0 )
			top --;
		sta[top ++] = p[i];
	}
	int t = top;
	for( int i = n - 2;~ i;i -- ) {
		while( top > t && cross( sta[top - 1] - sta[top - 2], p[i] - sta[top - 1] ) <= 0 )
			top --;
		sta[top ++] = p[i];
	}
	if( n > 1 ) top --;
	return top;
}

bool inpolygon_rad( point p, point *polygon, int n ) {
	int cnt = 0;
	for( int i = 0;i < n;i ++ ) {
		if( onseg( polygon[i], polygon[( i + 1 ) % n], p ) ) return 1;
		double c = cross( polygon[i] - p, polygon[( i + 1 ) % n] - p );
		double d1 = polygon[i].y - p.y, d2 = polygon[( i + 1 ) % n].y - p.y;
		if( c < 0 && d1 <= 0 && d2 > 0 ) cnt ++;
		if( c > 0 && d2 <= 0 && d1 > 0 ) cnt --;
	}
	return cnt != 0;
}

int main() {
	int n, m;
	next : 
	while( scanf( "%d %d", &n, &m ) ) {
		if( ! n && ! m ) return 0;
		for( int i = 0;i < n;i ++ )
			scanf( "%lf %lf", &R[i].x, &R[i].y );
		for( int i = 0;i < m;i ++ )
			scanf( "%lf %lf", &B[i].x, &B[i].y );
		n = graham( n, R, hallR );
		m = graham( m, B, hallB );
		for( int i = 0;i < n;i ++ )
			if( inpolygon_rad( R[i], hallB, m ) ) { printf( "No\n" ); goto next; }
		for( int i = 0;i < m;i ++ )
			if( inpolygon_rad( B[i], hallR, n ) ) { printf( "No\n" ); goto next; }
		for( int i = 0;i < n;i ++ )
			for( int j = 0;j < m;j ++ )
				if( seg_intersect( hallR[i], hallR[( i + 1 ) % n], hallB[j], hallB[( j + 1 ) % m] ) ) {
					printf( "No\n" );
					goto next;
				}
		printf( "Yes\n" );	
	}
	return 0;
}

Area——求凸包,求多变形面积,pick定理

problem

( 0 , 0 ) (0,0) (0,0)开始,题目给的相当于是 n n n​个向量帮忙搭建凸包

套用pick定理

#include <cmath>
#include <cstdio>
#include <vector>
using namespace std;

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	point operator + ( point p ) { return point( x + p.x, y + p.y ); }
	friend double cross( point s, point t ) { return s.x * t.y - s.y * t.x; }
};

vector < point > polygon;
int T, n;

double area() {
	double s = 0;
	for( int i = 0;i < n;i ++ )
		s += cross( polygon[i], polygon[( i + 1 ) % n] );
	return fabs( s );
}

int gcd( int x, int y ) {
	if( ! y ) return x;
	else return gcd( y, x % y );
}

int main() {
	scanf( "%d", &T );
	for( int Case = 1;Case <= T;Case ++ ) {
		polygon.clear();
		scanf( "%d", &n );
		point p( 0, 0 );
		polygon.push_back( p );
		for( int i = 1;i <= n;i ++ ) {
			double x, y;
			scanf( "%lf %lf", &x, &y );
			p = p + point( x, y );
			polygon.push_back( p );
		}
		if( Case > 1 ) printf( "\n" );
		printf( "Scenario #%d:\n", Case );
		double s = area(); int v = 0;
		for( int i = 0;i < n;i ++ ) {
			point p1 = polygon[i], p2 = polygon[( i + 1 ) % n];
			v += gcd( fabs( p1.x - p2.x ), fabs( p1.y - p2.y ) );
		}
		printf( "%d %d %.1f\n", ( int( s ) + 2 - v ) / 2 , v, s / 2 );
	}
	return 0;
}

That Nice Euler Circuit——欧拉定理

套用欧拉定理,注意重合点要删掉

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
#define eps 1e-9
#define maxn 305

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

struct vec {
	double x, y;
	vec(){}
	vec( double X, double Y ) { x = X, y = Y; }
	vec operator * ( double k ) { return vec( x * k, y * k ); }
	friend double dot( vec u, vec v ) { return u.x * v.x + u.y * v.y; }
	friend double cross( vec u, vec v ) { return u.x * v.y - u.y * v.x; }
};

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	point operator + ( vec v ) { return point( x + v.x, y + v.y ); }
	vec operator - ( point p ) { return vec( x - p.x, y - p.y ); }
}Po[maxn], V[maxn * maxn];

struct line {
	point p; vec v;
	line(){}
	line( point P, vec V ) { p = P, v = V; }
};

bool strict_intersect( point p1, point p2, point p3, point p4 ) {
	double s1 = cross( p2 - p1, p3 - p1 ), s2 = cross( p2 - p1, p4 - p1 );
	double s3 = cross( p4 - p3, p1 - p3 ), s4 = cross( p4 - p3, p2 - p3 );
	return dcmp( s1 ) * dcmp( s2 ) < 0 && dcmp( s3 ) * dcmp( s4 ) < 0;
}

point intersection( line l, line r ) {
	double s = cross( l.v, r.v );
	if( dcmp( s ) != 0 ) {
		double t = cross( r.v, l.p - r.p ) / s;
		return l.p + l.v * t;
	}
}

bool operator < ( const point &p1, const point &p2 ) {
	return p1.x == p2.x ? p1.y < p2.y : p1.x < p2.x;
}

bool operator == ( const point &p1, const point &p2 ) {
	return dcmp( p1.x - p2.x ) == 0 && dcmp( p1.y - p2.y ) == 0;
}

bool onseg( point l, point r, point p ) {
	return dcmp( cross( l - p, r - p ) ) == 0 && dcmp( dot( l - p, r - p ) ) < 0;
}

int main() {
	int n, T = 0;
	while( scanf( "%d", &n ) && n ) {
		for( int i = 0;i < n;i ++ )
			scanf( "%lf %lf", &Po[i].x, &Po[i].y ), V[i] = Po[i];
		n --; int cnt = n, E = n;
		for( int i = 0;i < n;i ++ )
			for( int j = i + 1;j < n;j ++ )
				if( strict_intersect( Po[i], Po[i + 1], Po[j], Po[j + 1] ) )
					V[cnt ++] = intersection( line( Po[i], Po[i + 1] - Po[i] ), line( Po[j], Po[j + 1] - Po[j] ) );
		sort( V, V + cnt );
		cnt = unique( V, V + cnt ) - V;
		for( int i = 0;i < cnt;i ++ )
			for( int j = 0;j < n;j ++ )
				if( onseg( Po[j], Po[j + 1], V[i] ) ) E ++;
		printf( "Case %d: There are %d pieces.\n", ++ T, 2 - cnt + E );
	}
	return 0;
}

Buried memory——求三角形的外心、最小圆覆盖

problem

#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
#define eps 1e-12

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	friend double len( point s, point t ) { return sqrt( ( s.x - t.x ) * ( s.x - t.x ) + ( s.y - t.y ) * ( s.y - t.y ) ); }
	point operator + ( point p ) { return point( x + p.x, y + p.y ); }
	point operator / ( double k ) { return point( x / k, y / k ); }
	double operator - ( point p ) { return x * x + y * y - p.x * p.x - p.y * p.y; }
}p[maxn];

double X( point p1, point p2 ) {
	return p1.x - p2.x;
}

double Y( point p1, point p2 ) {
	return p1.y - p2.y;
}

point get_c( point p1, point p2, point p3 ) {
	double t = 2 * ( Y( p2, p1 ) * X( p3, p1 ) - Y( p3, p1 ) * X( p2, p1 ) );
	return point( ( Y( p2, p1 ) * ( p3 - p1 ) - Y( p3, p1 ) * ( p2 - p1 ) ) / t, ( X( p3, p1 ) * ( p2 - p1 ) - X( p2, p1 ) * ( p3 - p1 ) ) / t );
}

int n; point c; double r;
void circle_cover() {
	random_shuffle( p + 1, p + n + 1 );
	c = p[1], r = 0;
	for( int i = 2;i <= n;i ++ )
		if( dcmp( len( c, p[i] ) - r ) > 0 ) {
			c = p[i], r = 0;
			for( int j = 1;j < i;j ++ )
				if( dcmp( len( c, p[j] ) - r ) > 0 ) {
					c = ( p[i] + p[j] ) / 2, r = len( c, p[i] );
					for( int k = 1;k < j;k ++ )
						if( dcmp( len( c, p[k] ) - r ) > 0 )
							c = get_c( p[i], p[j], p[k] ), r = len( c, p[i] );
				}
		}
}

int main() {
	while( scanf( "%d", &n ) && n ) {
		for( int i = 1;i <= n;i ++ )
			scanf( "%lf %lf", &p[i].x, &p[i].y );
		circle_cover();
		printf( "%.2f %.2f %.2f\n", c.x, c.y, r );
	}
	return 0;
}

「ICPC World Finals 2017」机场构建 Airport Construction——求凹凸多边形内的最长线段

source
在这里插入图片描述

#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 205
#define double long double
const double eps = 1e-7;
int n;
double ans;

int dcmp( double x ) {
	return fabs( x ) < eps ? 0 : ( x > 0 ? 1 : -1 );
}

struct vec {
	double x, y;
	vec(){}
	vec( double X, double Y ) { x = X, y = Y; }
	vec operator + ( vec v ) { return vec( x + v.x, y + v.y ); }
	vec operator - ( vec v ) { return vec( x - v.x, y - v.y ); }
	vec operator * ( double k ) { return vec( x * k, y * k ); }
	friend double len( vec v ) { return v.x * v.x + v.y * v.y; }
	friend double dis( vec v ) { return sqrt( len( v ) ); }
	friend double dot( vec u, vec v ) { return u.x * v.x + u.y * v.y; }
	friend double cross( vec u, vec v ) { return u.x * v.y - u.y * v.x; }
	friend bool order( vec v ) { return dcmp( v.y ) < 0 || ( dcmp( v.y ) == 0 && dcmp( v.x ) < 0 ); }//order规定一种指向 
	friend bool sameline( vec u, vec v ) { return dcmp( cross( u, v ) ) == 0; }//共线判断:两向量同向或反向 叉积面积为0 一维直线在二维的面积为0 
	friend bool samedirection( vec u, vec v ) { return sameline( u, v ) && dcmp( dot( u, v ) ) >= 0; }//同向判断:首先共线然后点积为非负->锐角 
};

struct point {
	double x, y;
	point(){}
	point( double X, double Y ) { x = X, y = Y; }
	point operator + ( point p ) { return point( x + p.x, y + p.y ); }
	point operator + ( vec v ) { return point( x + v.x, y + v.y ); }
	point operator / ( double k ) { return point( x / k, y / k ); }
	vec operator - ( point p ) { return vec( x - p.x, y - p.y ); }
}polygon[maxn]; 

struct line {
	point p; vec v;
	line(){}
	line( point P, vec V ) { p = P, v = V; }
}seg[maxn];

bool onseg( point l, point r, point p ) {
	return dcmp( cross( l - p, r - p ) ) == 0 && dcmp( dot( l - p, r - p ) ) <= 0;
}

bool onseg( line MS, point p ) {
	point l = MS.p, r = MS.p + MS.v;
	return onseg( l, r, p );
}

bool inpolygon( point p ) {
	int cnt = 0;
	for( int i = 0;i < n;i ++ ) {
		if( onseg( polygon[i], polygon[( i + 1 ) % n], p ) ) return 1;
		double c = cross( polygon[i] - p, polygon[( i + 1 ) % n] - p );
		double d1 = polygon[i].y - p.y, d2 = polygon[( i + 1 ) % n].y - p.y;
		if( c < 0 && d1 <= 0 && d2 > 0 ) cnt ++;
		if( c > 0 && d2 <= 0 && d1 > 0 ) cnt --;
	}
	return cnt != 0;
}

point seg_intersect( line l, point p1, point p2 ) {
	line r( p1, p2 - p1 );
	return l.p + l.v * ( cross( r.v, l.p - r.p ) / cross( l.v, r.v ) );
}

point p[maxn];

void solve( line MS ) {
	int cnt = 0;
	for( int i = 0;i < n;i ++ )
		if( sameline( MS.v, polygon[( i + 1 ) % n] - polygon[i] ) ) continue;//与int main里跳过线段同理 
		else {
			point intersection = seg_intersect( MS, polygon[i], polygon[( i + 1 ) % n] );
			if( onseg( polygon[i], polygon[( i + 1 ) % n], intersection ) ) p[++ cnt] = intersection;
			//如果线段与多边形的某些线段有交 记录下来
			//多边形可能是凹多边形 所以这种情况是可能出现的 
		}
	sort( p + 1, p + cnt + 1, []( point s, point t ) { return s.x == t.x ? s.y < t.y : s.x < t.x; } );
	int l = 1;
	while( l < cnt && ! onseg( MS, p[l]) ) l ++;
	int r = l;
	while( r < cnt && onseg( MS, p[r + 1] ) ) r ++;
	//两个while确定MS线段的首尾端点都被包含在多边形内 
	for( int i = l;i < r;i ++ )
		if( ! inpolygon( ( p[i] + p[i + 1] ) / 2 ) ) return;//然后新的整MS线段要被完全包含在多边形内才行 看图
	while( l > 1 && inpolygon( ( p[l] + p[l - 1] ) / 2 ) ) l --;//然后尽可能的往外拓展 p点是拍了序的 所以可以保证 
	while( r < cnt && inpolygon( ( p[r] + p[r + 1] ) / 2 ) ) r ++;
	ans = max( ans, dis( p[r] - p[l] ) );
}

int main() {
	scanf( "%d", &n );
	for( int i = 0, x, y;i < n;i ++ ) {
		scanf( "%d %d", &x, &y );
		polygon[i] = point( x, y ); 
	}
	for( int i = 0;i < n;i ++ ) {
		int ip = 0;
		for( int j = 0;j < n;j ++ )
			if( i == j || order( polygon[j] - polygon[i] ) ) continue;
			else seg[++ ip] = line( polygon[i], polygon[j] - polygon[i] );
		sort( seg + 1, seg + ip + 1, []( line l, line r ) {//极角排序建构多边形边集 
			int c = dcmp( cross( l.v, r.v ) );
			return c > 0 || ( c == 0 && dcmp( len( l.v ) - len( r.v ) ) < 0 );
		} );
		for( int j = 1;j <= ip;j ++ )
			if( j > 1 && sameline( seg[j - 1].v, seg[j].v ) ) continue;
			/*
			solve做的操作是把一条线段尽可能地边长撑到多边形边界为止 所以一个直线上的线段做一次就可以了 
			用共线sameline或者同向samedirection判断都是一样的
			因为已经极角排序过了->逆时针 
			*/ 
			else solve( seg[j] ); 
	}
	printf( "%.9Lf\n", ans );
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值