解题报告 (十一) 凸包

355 阅读6分钟

文章目录


凸包 解题报告

HDU 1348 Wall

链接:HDU 1348 Wall
题意:求一个凸包 和 圆 的周长和

  • 题解:注意两个点的周长,要来回算两遍;

HDU 1392 Surround the Trees

链接:HDU 1392 Surround the Trees
题意:求一个凸包的周长

  • 题解:求出凸包后,求多边形周长,需要考虑小于 3 个点的情况,两个点的情况只能算一遍;

PKU 3348 Cows

链接:PKU 3348 Cows
题意:求一个凸包的面积

  • 题解:求出凸包后求面积,然后除 50 之后取整即可;

PKU 1228 Grandpa’s Estate

链接:PKU 1228 Grandpa’s Estate
题意:给定一个凸多边形,丢失了一些点,问是否能够唯一确定一个凸多边形;

  • 题解:能够唯一确定一个凸多边形的条件是:凸包的每条边上都至少三个点,而且要求不在同一个位置。细节比较多,主要注意:
  • 1)点要去重;
  • 2)求凸包时,要求包括凸包上的点,Graham 极角扫描的时候因为相同极角的情况,最后一条边上的点是包含不进来的,需要特殊处理;
  • 3)求出的凸包,如果是一个点或者一条线,则一定是不满足的;
  • 4)对于每个凸包的拐角点,检查和上一个拐角点是否有其它点,一旦发现没有,则条件不满足,找点可以用叉乘共线;

HDU 3285 Convex Hull of Lattice Points

链接:HDU 3285 Convex Hull of Lattice Points
题意:给定一个凸多边形,求所有凸包上的点,按照顺时针方向输出;

  • 题解:题目要求第一个点是 y y y 轴最大的,可以将 y y y 轴坐标取相反数,然后求凸包,再在输出的时候再取相反数即可。

HDU 2907 Diamond Dealer

链接:HDU 2907 Diamond Dealer
题意:给定一个多边形,求它有多少个凹进去的块。

  • 题解:首先求一个凸包,然后把凸包上的点都标记出来,然后顺序扫描判断每条边:
  • 1)如果边上的两个点都被标记,说明这是一条凸包上的边;
  • 2)如果出现起点被标记,终点未被标记,说明是一个凹进去的块,计数器加一;
  • [注意:当一条边的起点和终点都未被标记时不用重复计数]

HDU 5533 Dancing Stars on Me

链接:HDU 5533 Dancing Stars on Me
题意:给定一些点,问是否能够组成一个正多边形。

  • 题解:首先求一个凸包,注意凸包边上的点不需要,然后进行如下判断:
  • 1)凸包点数和原点集个数不相等,则无法满足要求;
  • 2)点数小于3个,无法满足要求;
  • 3)判断所有边的长度,一旦有一条边和第一条边不等,则无法满足要求;
  • 4)叉乘判断凸多边形的每个角度,一旦有一个角和第一个角不等,则无法满足要求;
  • 5)否则,可以组成正多边形;

HDU 2406 Doors and Penguins

链接:HDU 2406 Doors and Penguins
题意:给定两组矩形,分别500个,问能否通过在中间加入一条直线把两组矩形分开。

  • 1)首先两组矩形分别求凸包;
  • 2)然后就是判定两个凸多边形是否相交了;
  • 判定规则为:
  • a)对于每条凸多边形上的边进行两两判交,一旦有一个相交就算相交;
  • b)对于一个凸多边形上的所有点在另一个凸多边形判断是否在多边形内,一旦出现就算相交(注意要互相判断,一共两次);

HDU 6325 Interstellar Travel

链接:HDU 6325 Interstellar Travel
题意:给定 n ( n < = 200000 ) n(n<=200000) n(n<=200000) 个点的点集 P ( x i , y i ) ( 1 < = i < = n , x i > = 0 , y i > = 0 ) P(x_i, y_i) (1<=i<=n, x_i >= 0, y_i >= 0) P(xi​,yi​)(1<=i<=n,xi​>=0,yi​>=0),其中 y 1 = y n = 0 , 0 = x 1 < x 2 , x 3 . . . , x n − 1 < x n y_1=y_n=0, 0=x_1 < x_2,x_3...,x_{n-1} < x_n y1​=yn​=0,0=x1​<x2​,x3​...,xn−1​<xn​ ,有重复点,如果现在在 ( x i , y i ) (x_i,y_i) (xi​,yi​),那么能够通过一次助力到达 ( x j , y j ) (x_j,y_j) (xj​,yj​),且 ( x i < x j ) (x_i < x_j) (xi​<xj​),消耗燃料 x i ∗ y j − x j ∗ y i x_i * y_j - x_j * y_i xi​∗yj​−xj​∗yi​,问从 P 1 P_1 P1​ 到 P n P_n Pn​ 最少消耗多少燃料,要求输出行进路径。

  • 题解:本质还是求一个凸包,可以这么考虑:四个点 P a , P b , P c , P d P_a, P_b, P_c,P_d Pa​,Pb​,Pc​,Pd​,如下图所示:
  • 路径1: a → b → c a \to b \to c a→b→c,消耗燃料: ( x a y b − x b y a ) + ( x b y c − x c y b ) (x_ay_b-x_by_a) + (x_by_c-x_cy_b) (xa​yb​−xb​ya​)+(xb​yc​−xc​yb​)
  • 路径2: a → c a \to c a→c,消耗燃料: ( x a y c − x c y a ) (x_ay_c-x_cy_a) (xa​yc​−xc​ya​)
  • 路径3: a → d → c a \to d \to c a→d→c,消耗燃料: ( x a y d − x d y a ) + ( x d y c − x c y d ) (x_ay_d-x_dy_a) + (x_dy_c-x_cy_d) (xa​yd​−xd​ya​)+(xd​yc​−xc​yd​)
  • 这样看不容易看出来,因为变参太多,我们观察燃料消耗其实是个叉乘运算,叉乘是向量减的运算,向量是可以随意平移的,所以我们可以把 ( x a , y a ) (x_a,y_a) (xa​,ya​) 平移到原点,即 ( x a , y a ) = ( 0 , 0 ) (x_a,y_a)=(0, 0) (xa​,ya​)=(0,0),这样问题就简单了。
  • 路径1的燃料消耗为: ( x a y b − x b y a ) + ( x b y c − x c y b ) = x b y c − x c y b (x_ay_b-x_by_a) + (x_by_c-x_cy_b) = x_by_c-x_cy_b (xa​yb​−xb​ya​)+(xb​yc​−xc​yb​)=xb​yc​−xc​yb​,向量 b c ⃗ \vec {bc} bc 是一个相对于 向量 a b ⃗ \vec {ab} ab 的左转向量,所以叉乘结果为正数;
  • 路径2 的燃料消耗为: ( x a y c − x c y a ) = 0 (x_ay_c-x_cy_a) = 0 (xa​yc​−xc​ya​)=0,这个好理解;
  • 路径3 的燃料消耗为: ( x a y d − x d y a ) + ( x d y c − x c y d ) = x d y c − x c y d (x_ay_d-x_dy_a) + (x_dy_c-x_cy_d) = x_dy_c-x_cy_d (xa​yd​−xd​ya​)+(xd​yc​−xc​yd​)=xd​yc​−xc​yd​,向量 d c ⃗ \vec {dc} dc 是一个相对于 向量 a d ⃗ \vec {ad} ad 的右转向量,所以叉乘结果为负数;
  • 根据以上结果得出,我们需要维护的是一个尽量上凸的曲线,也就是求一个凸包,这题坑比较多,如果采用传统的 Gramhan 极角来求会有很多考虑不到的因素,比如:三点共线、重复点、最后一条边上的共线点 等等;
  • 基于以上原因,可以直接按照 x x x 轴排序, x x x 轴相同则按照 y y y 轴排序,再相同则按照 i n d e x index index 排序,然后用一个单调栈 和 叉乘来维护一个(总是右转的)上凸曲线;求出凸包后需要注意一下几点:
  • 1)重复点只算下标最小的那个;
  • 2)如果有 y > 0 y > 0 y>0 的坐标存在,那么除了第一个点和最后一个点,其它 y = 0 y = 0 y=0 的点都要被剔除;
  • 3)然后,对凸包上的点按照 x x x 轴排序,利用单调栈处理共线点的下标单调递增即可;

HDU 3021 Tree Fence

链接:HDU 3021 Tree Fence
题意:给定一些木桩和一些树 (分别最多 200 个),要求将木桩绑上绳子围起来,围起来得到的凸多边形的边,每条边消耗 47 元,但是围起来的树,每棵可以得到 173 元,问用最优的方式来围,最大的收益是多少。

  • 题解:凸包 + 弗洛伊德最短路
  • 首先求一次凸包,然后把在凸包内的所有树都取出来,个数为 t t t。对于每个围栏 ( i , j ) (i, j) (i,j) 判断所有的树是否都在它的左边;如果是,则建立边 ( i → j ) (i \to j) (i→j),然后 O ( n 3 ) O(n^3) O(n3) 求一次弗洛伊德最短路,对于每个围栏的点 i i i,求它回到自己的最短路就是围掉所有树需要的边 x x x,答案就是 m a x ( 0 , 173 ∗ t − 47 ∗ x ) max(0, 173*t - 47*x) max(0,173∗t−47∗x)。

HDU 5928 Birthday Gift

链接:HDU 5928 Birthday Gift
题意:给定 n ( n < = 80 ) n( n <= 80 ) n(n<=80) 个点,要求用一根长度为 t t t 的绳子将一些点围起来,求最多能够围住多少个点。

  • 题解:动态规划
  • 因为两点之间线段最短,所以围起来的肯定不会是个曲线,肯定是一些折线段组成的封闭图形,而且不会是凹多边形,可以这么考虑,如果围成的是一个凹多边形,那么一定可以通过某种方式,使得围成的线更短,如下图所示:
  • 原理就是三角形两边之和大于第三边。图中凹进去的蓝色边一定可以用绿色的边来代替。
  • 1)首先以任意点作为左下角的原点,小于它的点都排除,建立极坐标系;
  • 2)用 d p [ i ] [ j ] dp[i][j] dp[i][j] 代表以第 O i Oi Oi 结尾的线段能够包围住除原点以外的 j j j 个点的最短长度,则有状态转移方程如下:
    d p [ i ] [ j ] = m i n ( d p [ k ] [ j − c n t O k i ] − d i s t ( 0 , k ) + d i s t ( i , k ) + d i s t ( 0 , i ) ) dp[i][j] = min( dp[k][j - cnt_{Oki}] - dist(0,k) + dist(i, k) + dist(0, i) ) dp[i][j]=min(dp[k][j−cntOki​]−dist(0,k)+dist(i,k)+dist(0,i)) ( 1 < = k < i ) (1 <= k < i) (1<=k<i)
  • 其中 c n t O k i cnt_{Oki} cntOki​ 代表 △ O k i \triangle Oki △Oki 内部有多少个点。
  • 第一层枚举 i i i,第二层枚举 k k k,第三层枚举有两个:一是枚举 ( k , i ) (k, i) (k,i) 之间所有的点 s s s,通过 k i ⃗ × k s ⃗ > 0 \vec {ki} \times \vec {ks} > 0 ki ×ks >0 代表在三角形内部,记为 c n t cnt cnt;二是枚举 j ∈ ( c n t , i ] j \in (cnt, i] j∈(cnt,i],通过状态转移计算 d p [ i ] [ j ] dp[i][j] dp[i][j] 的最小值。
  • d i s t ( i , j ) dist(i,j) dist(i,j) 代表点 i i i 和 点 j j j 的欧几里得距离,可以事先预处理出来。
  • 最坏时间复杂度为 O ( n 4 ) O(n^4) O(n4)。