文章目录

  • 点和向量及运算
  • 直线和线段
    • 求解点到直线的距离/点在直线上
    • 求解点到线段的距离/点在线段上
    • 求解两条线段是否相交
    • 求解两直线的交点
  • 多边形
    • 求解多边形面积
    • 求解多边形重心
    • 求解判断定点与多边形的位置关系
  • 凸包
    • graham扫描法
    • graham扫描法加强版
    • 求解圆与直线的交点
    • 求解圆与圆的交点
    • 求解定点与定圆的切线
    • 求解圆公切线
    • pick定理
    • 求解圆相交面积
    • 欧拉定理(图论)
    • 求解最小圆覆盖
    • 求解最近点对
    • 闵可夫斯基和(Minkowski Sum)
  • 欢迎来到——各大模板训练场!!!
    • Segments——判断线段相交
    • Points Within——点与多边形位置
    • Rotational Painting——找重心、求凸包、判断点在线段上
    • Professor's task——数据结构(set)维护动态凸包
    • The Great Divide——判断凸包是否相交
    • Area——求凸包,求多变形面积,pick定理
    • That Nice Euler Circuit——欧拉定理
    • Buried memory——求三角形的外心、最小圆覆盖
    • 「ICPC World Finals 2017」机场构建 Airport Construction——求凹凸多边形内的最长线段

点和向量及运算

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

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

向量的运算

定义向量A=(x1,y1),B=(x2,y2)A=(x_1,y_1),B=(x_2,y_2)A=(x1​,y1​),B=(x2​,y2​)

  • 加减:A±B=(x1±x2,y1±y2)A±B=(x_1±x_2,y1±y_2)A±B=(x1​±x2​,y1±y2​)

  • 数乘:kA=(kx1,ky1)kA=(kx_1,ky_1)kA=(kx1​,ky1​)

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

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

    tan⁡θ=yx→atan2⁡(y,x)=θ\tan\theta=\frac{y}{x}\rightarrow \operatorname{atan2}(y,x)=\thetatanθ=xy​→atan2(y,x)=θ

  • 点积/内积(⋅·⋅):A⋅B=x1x2+y1y2A·B=x_1x_2+y_1y_2A⋅B=x1​x2​+y1​y2​

    几何意义:AAA乘以BBB在AAA上的投影,A⋅B=∣A∣∣B∣cos⁡θA·B=|A||B|\cos\thetaA⋅B=∣A∣∣B∣cosθ

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

  • 叉积/外积(×\times×):A×B=∣x1y1x2y2∣=x1y2−x2y1=−B×AA\times B=\begin{vmatrix}x_1&y_1\\x_2&y_2\end{vmatrix}=x_1y_2-x_2y_1=-B\times AA×B=∣∣∣∣​x1​x2​​y1​y2​​∣∣∣∣​=x1​y2​−x2​y1​=−B×A

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

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

  • 旋转:把AAA逆时针旋转α\alphaα弧度得到新向量A′A'A′
    A=(x1,y1)=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α)=(rcos⁡θcos⁡α−rsin⁡θsin⁡α,rcos⁡θsin⁡α+rsin⁡θ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α)=(x1cos⁡α−y1sin⁡α,x1sin⁡α+y1cos⁡α)=(x_1\cos\alpha-y_1\sin\alpha,x_1\sin\alpha+y_1\cos\alpha) =(x1​cosα−y1​sinα,x1​sinα+y1​cosα)

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,xy,xy,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 ); }
};

直线和线段

直线的表示方式

  • 斜率式:kx+bkx+bkx+b

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

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

  • 一般式:ax+by+c=0ax+by+c=0ax+by+c=0

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

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

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

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

    优点:简单

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

  • 一个点和直线的方向向量
    设向量v=B−Av=B-Av=B−A,则直线ABABAB上的点可以表示为A+vtA+vtA+vt(ttt任取)

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

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

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

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

  • 用叉积的方法

    用叉积求出以p2p1p_2p_1p2​p1​和p2p0p_2p_0p2​p0​为邻边的平行四边形的面积,然后除以底p1p2p_1p_2p1​p2​的长度

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

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;}
};

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

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

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

∠p0p2p1\angle p_0p_2p_1∠p0​p2​p1​是钝角且∠p0p1p2\angle p_0p_1p_2∠p0​p1​p2​是锐角,则p0p_0p0​到线段p1p+2p_1p+2p1​p+2的距离是线段p0p2p_0p_2p0​p2​的长度

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

∠p0p1p2\angle p_0p_1p_2∠p0​p1​p2​是钝角且∠p0p2p1\angle p_0p_2p_1∠p0​p2​p1​是锐角,则p0p_0p0​到线段p1p2p_1p_2p1​p2​的距离是线段p0p1p_0p_1p0​p1​的长度

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

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 );
}

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

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

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;
}

求解两条线段是否相交

跨立实验

判断p1,p2p_1,p_2p1​,p2​是否在直线p3p4p_3p_4p3​p4​的两侧;再判断p3,p4p3,p4p3,p4是否在直线p1p2p_1p_2p1​p2​的两侧

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

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

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

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

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:p1+t(p2−p1)r:p3+s(p4−p3)l:p_1+t(p_2-p_1)\qquad r:p_3+s(p_4-p_3)l:p1​+t(p2​−p1​)r:p3​+s(p4​−p3​)

则交点可列等式p1+t(p2−p1)=p3+s(p4−p3)p_1+t(p_2-p_1)=p_3+s(p_4-p_3)p1​+t(p2​−p1​)=p3​+s(p4​−p3​)
⇒t(p2−p1)−s(p4−p3)=p3−p1⇒{x:t(x2−x1)−s(x4−x3)=x3−x1y:t(y2−y1)−s(y4−y3)=y3−y1\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(p2​−p1​)−s(p4​−p3​)=p3​−p1​⇒{x:y:​t(x2​−x1​)−s(x4​−x3​)=x3​−x1​t(y2​−y1​)−s(y4​−y3​)=y3​−y1​​

D0=∣x2−x1−(x4−x3)y2−y1−(y4−y3)∣=−∣x2−x1x4−x3y2−y1y4−y3∣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​=∣∣∣∣​x2​−x1​y2​−y1​​−(x4​−x3​)−(y4​−y3​)​∣∣∣∣​=−∣∣∣∣​x2​−x1​y2​−y1​​x4​−x3​y4​−y3​​∣∣∣∣​

Dt=∣x3−x1−(x4−x3)y3−y1−(y4−y3)∣=∣x1−x3x4−x3y1−y3y4−y3∣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​=∣∣∣∣​x3​−x1​y3​−y1​​−(x4​−x3​)−(y4​−y3​)​∣∣∣∣​=∣∣∣∣​x1​−x3​y1​−y3​​x4​−x3​y4​−y3​​∣∣∣∣​

Ds=∣x2−x1x3−x1y2−y1y3−y1∣=−∣x2−x1x1−x3y2−y1y1−y3∣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​=∣∣∣∣​x2​−x1​y2​−y1​​x3​−x1​y3​−y1​​∣∣∣∣​=−∣∣∣∣​x2​−x1​y2​−y1​​x1​−x3​y1​−y3​​∣∣∣∣​

u⃗=p2−p1=(x2−x1,y2−y1)v⃗=p4−p3=(x4−x3,y4−y3)w⃗=p1−p3=(x1−x3,y1−y3)\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=p2​−p1​=(x2​−x1​,y2​−y1​)v=p4​−p3​=(x4​−x3​,y4​−y3​)w=p1​−p3​=(x1​−x3​,y1​−y3​)

{D0=−u⃗×v⃗Ds=−u⃗×w⃗Dt=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×vDs​=−u×wDt​=w×v​

t=DtD0=v⃗×w⃗u⃗×v⃗s=DsD0=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=D0​Dt​​=u×vv×w​s=D0​Ds​​=u×vu×w​

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

否则可以根据ttt和sss的取值判断交点是否在线段上

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;
}

多边形

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

也可以按极角的规律顺序

求解多边形面积

  • 凸多边形

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

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

  • 凹多边形

    • 计算出△ABC\triangle ABC△ABC的面积

    • 计算出凹四边形ABCDABCDABCD的面积(实际上是△ACD−△ABC\triangle ACD-\triangle ABC△ACD−△ABC)

    • 计算出复杂五边形ABCDEABCDEABCDE的面积(实际上是四边形ABCD−△ADEABCD-\triangle ADEABCD−△ADE)

      PSPSPS:这个五边形不是简单多边形,简单多边形是由边不交的线段组成的多边形,这里AEAEAE和BCBCBC相交了,不妨设交点是GGG

      那么这里算的面积实际上是凹四边形GCDEGCDEGCDE的面积减去△AGB\triangle AGB△AGB的面积

    • 计算出凹六边形ABCDEFABCDEFABCDEF的面积(实际上是五边形ABCDE+△AEFABCDE+\triangle AEFABCDE+△AEF)

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

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

(发现ACACAC在ABABAB右边所以减,ADADAD在ACACAC左边所以加.........)

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

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

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 );
}

求解多边形重心

重心公式(x1+x2+x33,y1+y2+y33)(\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;
}
  • 转角法

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

    000:点在多边形外

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

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

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

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

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

    • 点III,点LLL

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

    • 点KKK,点JJJ

      因为多边形自带顶点顺序,满足ccc条件时,ddd条件恰好与ififif语句相反,程序判定正确

    • 点MMM

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

      FG→\overrightarrow{FG}FG,满足i+1i+1i+1在iii右边且i+1i+1i+1在点上方,iii在点下方,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;
    }
    
  • 改进的转角法

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

    • Pi+1P_{i+1}Pi+1​在PiP_iPi​的相同象限,弧度不变

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

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

    • Pi+1P_{i+1}Pi+1​​​在PiP_iPi​​​的相对象限,计算向量OPi→\overrightarrow{OP_i}OPi​​​​和OPi+1→\overrightarrow{OP_{i+1}}OPi+1​​​的叉积MSMSMS​

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

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

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

    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;
    }
    
  • 对于凸多边形

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

凸包

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

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

性质

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

graham扫描法

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

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

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

    极角:点与极轴的夹角

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

  • 先把p0,p1,p2p_0,p_1,p_2p0​,p1​,p2​入栈

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

  • 按极角排序枚举点

    • 先枚举GGG​

      看D−I−GD-I-GD−I−G发现III左拐到GGG,把GGG入栈

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

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

    • 然后枚举FFF

      I−G−FI-G-FI−G−F不转向,将GGG弹栈;D−I−FD-I-FD−I−F左转,不弹III,停止;将FFF入栈

    • 接着枚举HHH​

      I−F−HI-F-HI−F−H右转,将FFF弹栈;D−I−HD-I-HD−I−H左转,保留停止;将HHH入栈

    • 再枚举EEE

      I−H−EI-H-EI−H−E左转,将EEE入栈

    • 考虑AAA

      H−E−AH-E-AH−E−A右转,弹EEE;I−H−AI-H-AI−H−A左转,保留停止;入栈AAA

    • 最后是BBB

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

时间复杂度O(nlog⁡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,CA,B,CA,B,C​入栈
  • 然后DDD​入栈
  • 此时遇到AF,AEAF,AEAF,AE​极角相同,按距离先排FFF​
  • C−D−FC-D-FC−D−F​左转,FFF​入栈
  • D−F−ED-F-ED−F−E​右转,结果最后EEE​​​被弹出栈了

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

graham扫描法加强版

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

  • 前两个点p0,p1p_0,p_1p0​,p1​入栈

  • 枚举后面每个点

    如果栈内元素个数>1>1>1,设栈顶toptoptop,栈顶下一个lstlstlst

    考虑lst−top−pilst-top-p_ilst−top−pi​是否向左拐

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

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

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

e.g.

  • 所有点排序

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

  • 222弹栈,333进栈

  • 444进栈

  • 444出栈,555进栈

  • 555出栈,666进栈

  • 666出栈,777进栈

  • 888进栈

  • 888出栈,999​进栈

  • 999​​出栈,101010​​​进栈

  • 111111进栈

  • 111111​​出栈,121212​​​进栈

  • 121212出栈,131313进栈

  • 141414进栈

  • 得到下凸壳

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

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

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

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

圆的表示:圆心CCC和半径rrr

圆的一般方程形式:(x−x0)2+(y−y0)2=r2(x-x_0)^2+(y-y_0)^2=r^2(x−x0​)2+(y−y0​)2=r2​

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

求解圆与直线的交点

判定相交

算出圆心到直线的距离ddd

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

求解交点

算出垂足DDD​(已知直线ABABAB可得ADADAD直线方向向量,从AAA点走方向向量长度ddd

∣CD∣=∣BD∣=r2−d2|CD|=|BD|=\sqrt{r^2-d^2}∣CD∣=∣BD∣=r2−d2​

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

求解圆与圆的交点

判定相交:圆心距d≤r1+r2d\le r_1+r_2d≤r1​+r2​

求解交点

  • 联立方程求解

  • 几何法

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

    CACACA​直线已知,可求得CACACA​直线的单位方向向量ca→\overrightarrow{ca}ca​

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

求解定点与定圆的切线

切线数量

  • 点在圆内,无
  • 点在圆上,111条
  • 点在圆外,222条

求解切线

  • 数学解析法

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

  • 几何法

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

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

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

求解圆公切线

公切线数量判定

  • 重合:d=0d=0d=0,无数条
  • 内含:0<d<r2−r10<d<r_2-r_10<d<r2​−r1​,000条
  • 内切:d=r2−r1d=r_2-r_1d=r2​−r1​,111条
  • 相交:r2−r1<d<r2+r1r_2-r_1<d<r_2+r_1r2​−r1​<d<r2​+r1​,222条
  • 外切:d=r1+r2d=r_1+r_2d=r1​+r2​,111条
  • 外离:r1+r2<dr_1+r_2<dr1​+r2​<d,222条外公切线,222条内公切线

求解公切线

  • 重合,内含不用求

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

  • 两条公切线

    已知∣AC∣=d,∣AD∣=r1,∣CE∣=r2|AC|=d,|AD|=r_1,|CE|=r_2∣AC∣=d,∣AD∣=r1​,∣CE∣=r2​,垂足为D,ED,ED,E

    相似求解FAFC=ADCE=FDFE\frac{FA}{FC}=\frac{AD}{CE}=\frac{FD}{FE}FCFA​=CEAD​=FEFD​,∣AF∣,∣FD∣,∣DE∣|AF|,|FD|,|DE|∣AF∣,∣FD∣,∣DE∣长度可得

    余弦定理得∠AFD\angle AFD∠AFD

    CACACA直线已知,从AAA出发走ca⃗\vec{ca}ca长度∣AF∣|AF|∣AF∣得到两外公切线交点FFF

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

    (内切线同理)

pick定理

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

其中aaa表示多边形内部的点数,bbb表示多边形边界上的点数,SSS表示多边形的面积

一般是反过来使用求aaa

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

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

设线段(x1,y1)−(x2,y2)⇒cnt=gcd⁡(∣x2−x1∣,∣y2−y1∣)+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(∣x2​−x1​∣,∣y2​−y1​∣)+1

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

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

求解圆相交面积

#include <bits/stdc++.h>
using namespace std;
#define eps 1e-10double 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 );
}

欧拉定理(图论)

设GGG为任意联通的平面图,nnn表示顶点数,mmm表示边数,rrr表示面数(去除封闭面后剩下的“背景”也算完整的一面)

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

求解最小圆覆盖

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

定义CiC_iCi​​为覆盖前iii​​个点的最小包围圆

  • pi∈Ci−1p_i\in C_i-1pi​∈Ci​−1​,则Ci=Ci−1C_i=C_{i-1}Ci​=Ci−1​​

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

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

    如果存在pj∉Cip_j\notin C_ipj​∈/​Ci​,则Ci={pj,pi}C_i=\{p_j,p_i\}Ci​={pj​,pi​}

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

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

    当kkk​​​​​的循环完成时,退出循环,更新pjp_jpj​​​​​

    当jjj​​​的循环结束时,退出循环,更新pip_ipi​​​

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

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

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

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

  • 先枚举AAA​​​​,ADADAD​​​构成圆的直径,假如BBB​​不在◯AD\bigcirc AD◯AD​内
  • 让BDBDBD​​​​构成圆的直径
  • 假如AAA​​​不在◯BD\bigcirc BD◯BD​​内,让ABDABDABD​构成一个圆
  • 假如CCC​​​不在◯ABD\bigcirc ABD◯ABD​​内,让CDCDCD​构成圆的直径
  • 假如AAA​​​不在◯CD\bigcirc CD◯CD​​内,让ACDACDACD​构成一个圆
  • 假如BBB​​​不在◯ACD\bigcirc ACD◯ACD​​内,让BCDBCDBCD​构成一个圆

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

事实上是没有的

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

  • 设ABCDABCDABCD​是凹四边形

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

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

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

  • 设ABCDABCDABCD​​是凸四边形

    假设四点共圆,那么对角和为180∘180^\circ180∘​

    如果某个点在此圆外

    由四边形的内角和为360∘360^\circ360∘,一组对角和小于180∘180^\circ180∘,另外一组对角和大于180∘180^\circ180∘​

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

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

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

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

时间复杂度

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

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

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

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

#define eps 1e-12int 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个点,求最近点对

分治

  • 对点按xxx坐标排序,作一条竖线x=midx=midx=mid​​​​​​​​​​,把点集分成两半,LLL​​​​​​​​​和RRR​​

    假设现在已经求出LLL​​​​​的最近点对距离dld_ldl​​​​​,RRR​​​的最近点对距离drd_rdr​​​

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

  • 考虑LLL中的点和RRR中的点的最短距离如何求

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

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

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

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

    • 把PPP中的点按照yyy排序

      取定PPP​​中的点pip_ipi​​​,枚举到pjp_jpj​​,使得ypj+1−ypi≥dy_{p_{j+1}}-y_{p_i}\ge dypj+1​​−ypi​​≥d​,就不枚举了

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

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

      所以在重复点去掉的情况下, 对于任意pip_ipi​,满足ypj−ypi<dy_{p_{j}}-y_{p_i}< dypj​​−ypi​​<d​​​​​的点只有常数个

闵可夫斯基和(Minkowski Sum)

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

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

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

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

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

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

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

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

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

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

  • 假设现在做到AiA_iAi​和BjB_jBj​两个点,把Ai+BjA_i+B_jAi​+Bj​放入集合
  • 用叉积比较Ai+1−Ai,Bj+1−BjA_{i+1}-A_i,B_{j+1}-B_jAi+1​−Ai​,Bj+1​−Bj​​​的极角序,极角序较小的指针+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 ++;elsePo[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,ba,ba,b
  • 这两个点作差得到d→=a−b\overrightarrow{d}=a-bd=a−b
    那么由a,ba,ba,b​​的任意性,对于所有a,ba,ba,b,对d→\overrightarrow{d}d​取最小值就是答案
  • 把BBB​​凸包的点全部取反(横纵坐标取相反数)得到B′B'B′​​
  • 计算闽可夫斯基和A+B′A+B'A+B′​,实际上计算的是A−BA-BA−B
  • 然后计算点(0,0)(0,0)(0,0)​到凸包A−BA-BA−B​的最短距离即可

求定点到凸包的最短距离

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

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

Segments——判断线段相交

problem

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

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

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

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

#include <cstdio>
#include <cmath>
using namespace std;
#define eps 1e-8
#define maxn 105struct 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-8int 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-10int 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 longstruct 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 > :: iteratorit 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 505int 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)开始,题目给的相当于是nnn​个向量帮忙搭建凸包

套用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 305int 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-12int 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;
}

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

  1. 六套苹果CMSv10首涂手机视频站模板高端主题代码精简优化SEO

    第六套苹果CMSv10首涂手机视频站模板原创主题代码精简优化SEO 首款纯手机模板,完美适配首款海洋cms手机模板原创手机主题模板经典精修专业视频站模板首选!体量超小运行流畅不卡顿,有着很好的用户体验 ...

  2. 桥梁远景图微型计算机教案,小学六年级语文《桥梁远景图》教案模板

    <桥梁远景图>是以科学精神进行大胆而合理的想象,对未来桥梁做出形象描述的说明文.下面就是小编给大家带来的小学六年级语文<桥梁远景图>教案模板,希望能帮助到大家! 小学六年级语文 ...

  3. 由浅入深:自己动手开发模板引擎——置换型模板引擎(四)

    受到群里兄弟们的竭力邀请,老陈终于决定来分享一下.NET下的模板引擎开发技术.本系列文章将会带您由浅入深的全面认识模板引擎的概念.设计.分析和实战应用,一步一步的带您开发出完全属于自己的模板引擎.关于 ...

  4. 由浅入深:自己动手开发模板引擎——置换型模板引擎(三)

    受到群里兄弟们的竭力邀请,老陈终于决定来分享一下.NET下的模板引擎开发技术.本系列文章将会带您由浅入深的全面认识模板引擎的概念.设计.分析和实战应用,一步一步的带您开发出完全属于自己的模板引擎.关于 ...

  5. 【C++ 泛型编程 进阶篇】 C++ 泛型编程 模板与异常处理、模板与友元之间的使用

    这里写目录标题 1. 简介 1.1 模板的基础理念 1.2 异常处理的基础理念 2. 模板与异常处理 2.1 异常处理在模板中的基本应用 2.1.1 为模板函数/类添加异常处理 2.1.2 使用noe ...

  6. 由浅入深:自己动手开发模板引擎——置换型模板引擎(二)

    受到群里兄弟们的竭力邀请,老陈终于决定来分享一下.NET下的模板引擎开发技术.本系列文章将会带您由浅入深的全面认识模板引擎的概念.设计.分析和实战应用,一步一步的带您开发出完全属于自己的模板引擎.关于 ...

  7. 由浅入深:自己动手开发模板引擎——置换型模板引擎(一)

    受到群里兄弟们的竭力邀请,老陈终于决定来分享一下.NET下的模板引擎开发技术.本系列文章将会带您由浅入深的全面认识模板引擎的概念.设计.分析和实战应用,一步一步的带您开发出完全属于自己的模板引擎.关于 ...

  8. 2021年最新友价商城源码 优化实名认证模块 19套PC模板2套手机模板 服务市场 任务大厅

    优惠购买请至龙坤商城:2021年10月20日最新友价商城源码 优化实名认证模块 19套PC模板2套手机模板 服务市场 任务大厅 - 龙坤商城http://mall.loncons.com/produc ...

  9. 英语计算机答题卡模板,英语答题卡模板.doc

    英语答题卡模板 英语答题卡模板 篇一:中考英语答题卡模板 文档类型:上传人:李功随下载许可:是下载次数:33大小:188K所需贡献值:2 篇二:英语答题卡模板 小学五年级第一学期期中英语教学质量检测试 ...

最新文章

  1. gridContro使用随记
  2. c文本框只能输入数字_VBA代码限制文本框的输入
  3. Codeforces数学1600----day1[同余定理,树状数组+两次二分,,组合计数]
  4. x86_64 ubuntu18.04.4 python3.6.9 安装 cuda8.0 和 cudnn5.1
  5. 记录一下Memcached的用法:
  6. MySQL exists后面的子查询使用
  7. where 1=1低效?
  8. 数学天桥之中值定理|北京有文化的天桥
  9. 学习Windows2008——常用工具及命令(包括核心版部分命令)
  10. 使用nginx为ArcGIS Server做反向代理
  11. android实现横向选择器,安卓开发 简单实现自定义横向滚动选择View : HorizontalselectedView...
  12. matlab 积分后带int,matlab int 积分
  13. 3D变化——旋转的立方体
  14. hash与history 以及区别
  15. 圣诞表白html,圣诞节表白,最浪漫的表白方式
  16. canvas student下载_悉大官方学生系统万能的Canvas和Sydney Student,你真的会用吗?官网平台两步走+实用功能型Tips!...
  17. centos7 搭建redis 5.0.0 集群
  18. mysql5.7.17.zip教程,Mysql学习win10下mysql 5.7.17 zip压缩包版安装教程
  19. 缓存与分布式锁——场景实现
  20. Java 调用 扫描仪的技术该如何选择?

热门文章

  1. 资料分享 | python机器学习教程分享来袭
  2. promtail 配置详解_基于loki+promtail+grafana技术的日志集合
  3. python实战excel_实战python 读写EXCEL表
  4. dw自动滚动图片_3分钟搞定图片懒加载
  5. 查看linux的计划任务日志,查看计划任务日志(共5篇).docx
  6. 谷歌浏览器安卓_安卓免费时代结束,国产手机或将集体涨价?
  7. mysql数据聚合技术_Mysql 去重 聚合
  8. python 防止转义_python字符串前加r、f、u、l 的区别
  9. leetcode714.买卖股票的
  10. Java中关于单核处理多个线程的认识与了解