输入:位于同一平面的n个点的集合。
- 假设没有两点具有相同的横坐标
- 假设没有两点具有相同的纵坐标
- 假设没有三点共线
输出:一个表示凸包的集合。可以为点集或者边集
相关题目:
- 洛谷:P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包
- LeetCode:587. 安装栅栏
凸多边性:连接凸多边形中的任意两点,均在该凸多边形内部。
图中左边的即为凸多边形,右边的就不是凸多边形。
平面上某点集的凸包:包含所有点的最小凸多边形。
如下图所示,这就是一个典型的某个点集的凸包。
凸包的表示方法:一般会使用该凸包的所有的顶点来表示:
向量的叉积:向量 P 1 = ( x 1 , y 1 ) P_1=(x_1, y_1) P1=(x1,y1) 和 向量 P 2 = ( x 2 , y 2 ) P_2=(x_2, y_2) P2=(x2,y2),则 P 1 ∗ P 2 = x 1 y 2 − x 2 y 1 P_1 * P_2 = x_1y_2 - x_2y_1 P1∗P2=x1y2−x2y1
向量叉积的运用:将向量 P 1 P_1 P1放在 P 2 P_2 P2之前, P 2 P_2 P2相对 P 1 P_1 P1是向左转还是向右转,由他们的叉积决定:
向量叉积在凸包中的运用:给定线段AB和点C,判断AB与C的位置关系。
计算叉积: ( x B − x A , y B − y A ) ∗ ( x C − x B , y C − y B ) (x_B - x_A, y_B - y_A) * (x_C - x_B, y_C - y_B) (xB−xA,yB−yA)∗(xC−xB,yC−yB):
就如下图的结果:
- C在AB左侧: ( x B − x A , y B − y A ) ∗ ( x C − x B , y C − y B ) > 0 (x_B - x_A, y_B - y_A) * (x_C - x_B, y_C - y_B) > 0 (xB−xA,yB−yA)∗(xC−xB,yC−yB)>0
- E与AB共直线: ( x B − x A , y B − y A ) ∗ ( x E − x B , y E − y B ) = 0 (x_B - x_A, y_B - y_A) * (x_E - x_B, y_E - y_B) = 0 (xB−xA,yB−yA)∗(xE−xB,yE−yB)=0
- D在AB右侧: ( x B − x A , y B − y A ) ∗ ( x D − x B , y D − y B ) < 0 (x_B - x_A, y_B - y_A) * (x_D - x_B, y_D - y_B) < 0 (xB−xA,yB−yA)∗(xD−xB,yD−yB)<0
输入:n个坐标点,并且不存在三个点共线的情况。
输出:凸包的构成:坐标集合、边集合。
对每一个点,判断其是否在其余任意三个点构成的三角形中。
时间复杂度
穷举每个点的时间复杂度: O ( n ) O(n) O(n)、穷举除该点之外的其他三个点来构成的三角形的时间复杂度: O ( n 3 ) O(n^3) O(n3)
总的时间复杂度: O ( n 4 ) O(n^4) O(n4)
枚举所有的边,再判断剩余的点与边的位置。
时间复杂度
穷举每一条边的时间复杂度: O ( n 2 ) O(n^2) O(n2)。 对于边的判断的时间复杂度: O ( n ) O(n) O(n)
总的时间复杂度: O ( n 3 ) O(n^3) O(n3)
分解:将 n 个点分成 A 和 B 两个部分,其中 A 包含 n - 1个点, B 只包含一个点。
- 选取一个特别的点 q 放入 B :q 是所有点中最右侧的点,该点一定是当前点集中凸包的一部分。
- 不断对 A 进行上述的分解,直至只剩下三个点。
递归过程:对 A不断进行上述的分解,直至递归到基本情况:只剩下三个点。每一次递归,都是求解点集 A 的凸包 CH(A)
递归合并:利用向量的叉积,将 CH(A) 与 B 进行合并,求解整体的凸包。
递归分解
输入如图所示的 10 个点,先将其分解成 A 和 B 两个部分。
此时 A 包含 9 个点,B 包含 1 个点。CH(A) 很明显就是图中的连线部分,但真正求解过程中我们需要不断的将其分解,直至达到基本情况。
中间部分分解过程,如下图所示:
最终,将 A 分解到基本情况:
三个点,一定都属于凸包,即为CH(A),因此不需要再进行划分。此时不需要向下递归,开始进行递归合并 :即CH(A) 和 B的合并。
合并凸包
凸包的支撑线:与凸包仅相交于⼀点的直线。
过凸包外一点,仅能得到两条支撑线,如图中的 q p 5 qp_5 qp5 和 q p 3 qp_3 qp3。
右支撑线: 凸包剩余点及内部的点均在支撑线的下方。
左支撑线:凸包剩余点及内部的点均在支撑线的上方。
q与凸包两交点 p 3 p_3 p3、 p 5 p_5 p5 将凸包分成两部分
近链:靠近 q q q 的部分
远链:远离 q q q 的部分
通过观察,可得到,新的凸包,是由 远链 和 左右支撑线 来决定的。
因此合并的主要过程也就是寻找 左右支撑线 。
如果直接枚举求左右支撑线,总共需要 O ( n 2 ) O(n^2) O(n2)的时间复杂度。
可以观察到:凸包上所有的点都在左支撑线的右侧(除左支撑线上的 p 5 p_5 p5)。
进一步归纳,发现只需要判断凸包与左支撑线交点的相邻两个点( p 1 p_1 p1、 p 4 p_4 p4)是否在该左支撑线的右侧即可。
寻找左支撑线:从距离q最近的 p i p_i pi 开始,判断 p ( i + 1 ) % n p_{(i+1)\%n} p(i+1)%n 和 p ( i − 1 ) % n p_{(i-1)\%n} p(i−1)%n 是否在左支撑线的右侧。
寻找右支撑线:从距离q最近的 p i p_i pi 开始,判断 p ( i + 1 ) % n p_{(i+1)\%n} p(i+1)%n 和 p ( i − 1 ) % n p_{(i-1)\%n} p(i−1)%n 是否在左支撑线的左侧。
将q 与 远链中包含的点 构成递归的返回值,即递归过程中新的凸包。
时间复杂度
总的时间复杂度 : T ( n ) = T ( n − 1 ) + T m e r g e T ( n ) = O ( n ) T m e r g e T(n) = T(n-1) + T_{merge}\\T(n)=O(n) T_{merge} T(n)=T(n−1)+TmergeT(n)=O(n)Tmerge
而 T m e r g e T_{merge} Tmerge 的主要时间花销就在寻找左右支撑线上,因此 T m e r g e = O ( n ) T_{merge} = O(n) Tmerge=O(n)
所以总的时间复杂度: T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)
分解 :将 n 个点按照横坐标排序,然后将其分成 A、B两部分,每个部分均包含 n/2 个点。
递归过程:递归求解 A 和 B 的凸包 P和Q。
- 基本情况:不能只考虑 3 个点的情况,还需要考虑会出现 2 个点的情况。
递归合并:对凸包P和Q进行合并。
递归过程
输入 11 个点,第一轮递归过程如下图所示:
对 A 再次递归就会出现基本情况:
此时将 A 均分成两个部分,这两个部分分别有2和3个点,这两种情况均为基本情况,直接返回这些点集即可。
合并凸包
对于递归得到的两个凸包:P 和 Q。
上桥:两个凸包上的两点的连线,且两个凸包剩余的点及凸包内部的所有点均位于上桥的下方。如 a 1 b 1 a_1b_1 a1b1
下桥:两个凸包上的两点的连线,且两个凸包剩余的点及凸包内部的所有点均位于上桥的上方。如 a 3 b 3 a_3b_3 a3b3
右链:某一凸包与上桥的交点按顺时钟到该凸包与下桥的交点中间的点与线。
左链:某一凸包与上桥的交点按逆时钟到该凸包与下桥的交点中间的点与线。
因此合并 P 和 Q 产生的新的凸包 = P的左链 + 上桥 + Q的右链 + 下桥
合并凸包的主要过程就是寻找上桥和下桥。
观察得:
上桥 a 1 b 1 a_1b_1 a1b1是 b 1 b_1 b1的右支撑线,是 a 1 a_1 a1的左支撑线。
下桥 a 3 b 3 a_3b_3 a3b3是 b 3 b_3 b3的左支撑线,是 a 3 a_3 a3的右支撑线。
寻找上桥:
令 a i a_i ai是 P 中距离 Q 最近的点, b j b_j bj是 Q 中距离 P 最近的点。
重复上述的两个步骤,直至找到上桥。
寻找下桥的方式类似,不过方向相反!
初始点为 a 2 a_2 a2 和 b 1 b_1 b1
- a 2 b 1 a_2b_1 a2b1是 a 2 a_2 a2的左支撑线。
- a 2 b 1 a_2b_1 a2b1不是 b 1 b_1 b1的右支撑线,逆时钟检查 a 1 a_1 a1。
- a 1 b 1 a_1b_1 a1b1是 b 1 b_1 b1的右支撑线。
- a 1 b 1 a_1b_1 a1b1是 a 1 a_1 a1的左支撑线。
- 满足条件, a 1 b 1 a_1b_1 a1b1即为P和Q的上桥。
时间复杂度
寻找上下桥的时间复杂度,最坏情况就是遍历一遍两边节点之和: O ( n ) O(n) O(n),因此 T m e r g e = O ( n ) T_{merge} = O(n) Tmerge=O(n)
总时间复杂度: T ( n ) = 2 T ( n / 2 ) + T m e r g e T ( n ) = O ( n l o g n ) T(n) = 2T(n/2) + T_{merge}\\T(n) = O(nlogn) T(n)=2T(n/2)+TmergeT(n)=O(nlogn)
分解:选择最左和最右两点的连线 a b ab ab 将其分成两个部分 A 和 B,这两点一定属于凸包。再在A和B中选择距离 a b ab ab 最远的点,构成两个三角形,该两点也一定属于凸包。三角形将对应的区域再次划分,三角形内部的点一定不属于凸包,对三角形外部的点进行递归。
递归流程
首先对输入的点集进行预排序,按照x坐标进行排序。
选择最左的点 a ,最右的点 b(这两点一定属于凸包)。
此时 a b ab ab将整个点集分成上下两个部分,A和B。
一直重复上述递归直至结束。
可以将整个递归流程划分成两个步骤:
quick_hull(S):
(a,b) <-- sorted_points(S)
A <-- right_of(S, ba)
B <-- right_of(S, ab)
Qa <-- quick_half_hull(A, ba)
Qb <-- quick_half_hull(B, ab)
return {a} ∪ Qa ∪ {b} ∪ Qb
quick_half_hull(S, ba):
if(S == Ø) return Ø
c <-- furthest(S, ba)
A <-- right_of(S, bc)
B <-- right_of(S, ca)
Qa <-- quick_half_hull(A, bc)
Qb <-- quick_half_hull(B, ca)
return Qa ∪ {c} ∪ Qb
假设 |S| = n、|A|=a、|B|=b。
q u i c k _ h a l f _ h u l l ( ) quick\_half\_hull() quick_half_hull() 的 运行时间: T ( n ) = T ( a ) + T ( b ) + O ( n ) T(n)=T(a) + T(b) + O(n) T(n)=T(a)+T(b)+O(n)
q u i c k _ h u l l ( ) quick\_hull() quick_hull() 的运行时间:预排序 + 计算A和B + q u i c k _ h a l f _ h u l l ( ) quick\_half\_hull() quick_half_hull()的递归时间
因此总时间复杂度:
输入:两个n-1次多项式。输入的是从0~n-1次项的系数。
输出:一个2n-2次多项式。输出的是0~2n-2次项的系数。
相关题目:
- 洛谷:P3803 【模板】多项式乘法(FFT)
对于多项式的加法,只需要将相同次数的系数相加即可,时间复杂度: O ( n ) O(n) O(n)
n个系数,分别和另一多项式的n个系数相乘,再对应相加。每一个系数都需要 O ( n ) O(n) O(n) 次乘法, O ( n ) O(n) O(n)次加法。
总体时间复杂度: O ( n 2 ) O(n^2) O(n2)
按照 x x x 的次数的高低,将 A A A 和 B B B 分成两个部分:
A = ∑ i = 1 n − 1 a i x i = A 1 + A 2 x n / 2 A = \sum_{i=1}^{n-1} a_ix^i = A_1 + A_2x^{n/2} A=∑i=1n−1aixi=A1+A2xn/2,其中 A 1 = ∑ i = 0 n / 2 − 1 a i x i A_1=\sum_{i=0}^{n/2-1} a_ix^i A1=∑i=0n/2−1aixi, A 2 = ∑ i = 0 n / 2 − 1 a i + n / 2 x i A_2=\sum_{i=0}^{n/2-1} a_{i+n/2}x^i A2=∑i=0n/2−1ai+n/2xi
B = ∑ i = 1 n − 1 b i x i = B 1 + B 2 x n / 2 B = \sum_{i=1}^{n-1} b_ix^i = B_1 + B_2x^{n/2} B=∑i=1n−1bixi=B1+B2xn/2,其中 B 1 = ∑ i = 0 n / 2 − 1 b i x i B_1=\sum_{i=0}^{n/2-1} b_ix^i B1=∑i=0n/2−1bixi, B 2 = ∑ i = 0 n / 2 − 1 b i + n / 2 x i B_2=\sum_{i=0}^{n/2-1} b_{i+n/2}x^i B2=∑i=0n/2−1bi+n/2xi
此时 A B = ( A 1 + A 2 x n / 2 ) ∗ ( B 1 + B 2 x n / 2 ) = A 1 B 1 + ( A 1 B 2 + A 2 B 1 ) x n / 2 + A 2 B 2 x n AB = (A_1 + A_2x^{n/2}) * (B_1 + B_2x^{n/2}) = A_1B_1 + (A_1B_2 + A_2B_1)x^{n/2} + A_2B_2x^n AB=(A1+A2xn/2)∗(B1+B2xn/2)=A1B1+(A1B2+A2B1)xn/2+A2B2xn
A B AB AB 是两个 n-1 次多项式的相乘
A i B j A_iB_j AiBj 则是两个 n/2 -1 次多项式相乘
时间复杂度
因此总的时间复杂度: T ( n ) = 4 T ( n / 2 ) + O ( n ) T(n) = 4T(n/2) + O(n) T(n)=4T(n/2)+O(n)
令 T ( n / 2 k ) = O ( 1 ) T(n/2^k) = O(1) T(n/2k)=O(1) 即 k = l o g 2 n k=log_2n k=log2n
所以 T ( n ) = 4 l o g 2 n + k O ( n ) T ( n ) = O ( n 2 ) T(n)=4^{log_2n}+kO(n)\\T(n)=O(n^2) T(n)=4log2n+kO(n)T(n)=O(n2)
与直接计算的时间复杂度相同,因此不够优。
A B = ( A 1 + A 2 x n / 2 ) ∗ ( B 1 + B 2 x n / 2 ) = A 1 B 1 + ( A 1 B 2 + A 2 B 1 ) x n / 2 + A 2 B 2 x n AB = (A_1 + A_2x^{n/2}) * (B_1 + B_2x^{n/2}) = A_1B_1 + (A_1B_2 + A_2B_1)x^{n/2} + A_2B_2x^n AB=(A1+A2xn/2)∗(B1+B2xn/2)=A1B1+(A1B2+A2B1)xn/2+A2B2xn
令 A 1 B 1 = P A_1B_1=P A1B1=P、 A 1 B 2 = R A_1B_2=R A1B2=R、 A 2 B 1 = Q A_2B_1=Q A2B1=Q、 A 2 B 2 = S A_2B_2=S A2B2=S
即 A B = P + ( R + Q ) x n / 2 + S x n AB=P + (R + Q)x^{n/2}+Sx^n AB=P+(R+Q)xn/2+Sxn
再令 U = ( A 1 + A 2 ) ( B 1 + B 2 ) = P + R + Q + S U = (A_1+A_2)(B_1+B_2) = P + R + Q + S U=(A1+A2)(B1+B2)=P+R+Q+S
所以 R + Q = U − P − S R + Q = U - P -S R+Q=U−P−S
因此 A B = P + ( U − P − S ) x n / 2 + S AB = P + (U - P - S)x^{n/2} + S AB=P+(U−P−S)xn/2+S
相比于计算 A 1 B 1 A_1B_1 A1B1、 A 1 B 2 A_1B_2 A1B2 、 A 2 B 1 A_2B_1 A2B1、 A 2 B 2 A_2B_2 A2B2这四个不同的值,计算 P P P、 U − P − S U - P - S U−P−S、 S S S 只需要得到 P P P、 U U U、 S S S这三个不同的值。
此时 4 次递归,减少到了 3 次递归。
时间复杂度
因此总的时间复杂度: T ( n ) = 3 T ( n / 2 ) + O ( n ) T(n) = 3T(n/2) + O(n) T(n)=3T(n/2)+O(n)
令 T ( n / 2 k ) = O ( 1 ) T(n/2^k) = O(1) T(n/2k)=O(1) 即 k = l o g 2 n k=log_2n k=log2n
所以 T ( n ) = 3 l o g 2 n + k O ( n ) T ( n ) = O ( n l o g 2 3 ) = O ( n 1.585 ) T(n)=3^{log_2n}+kO(n)\\T(n)=O(n^{log_23}) = O(n^{1.585}) T(n)=3log2n+kO(n)T(n)=O(nlog23)=O(n1.585)
输入:两个 n*n 的矩阵 X与Y
输出:输出两个矩阵的乘积 Z=XY
相关题目:
- 洛谷:B2105 矩阵乘法
根据矩阵乘法的定义: z i j = ∑ k = 1 n x i k y k j z_{ij} = \sum_{k=1}^{n}x_{ik}y_{kj} zij=∑k=1nxikykj
枚举i、j、k,计算对应的 z i j z_{ij} zij。
因此总的时间复杂度: T ( n ) = O ( n 3 ) T(n) = O(n^3) T(n)=O(n3)
将矩阵分块,将一个 n*n 的矩阵划分成 4 个 n/2 * n/2 的矩阵。
原矩阵的乘法可以基于分块矩阵的乘法来完成。
例如:两个 4*4 的矩阵相乘,将这两个 4*4 的矩阵划分成四个 2*2 的分块矩阵,再分别相乘。
计算结果如下图所示:
n*n 的矩阵划分成 4 个 n/2*n/2 的矩阵,矩阵相乘则有 8 个 n/2*n/2的矩阵相乘 + 4个 n/2*n/2 的矩阵相加。
时间复杂度: T ( n ) = 8 T ( n / 2 ) + O ( n 2 ) T ( n ) = O ( n 3 ) T(n) = 8T(n/2) + O(n^2) \\ T(n) = O(n^3) T(n)=8T(n/2)+O(n2)T(n)=O(n3)
将8个 n/2*n/2的矩阵相乘 + 4个 n/2*n/2 的矩阵相加,优化成7个 n/2*n/2的矩阵相乘 + 11个n/2*n/2 的矩阵相加 + 7个n/2*n/2 的矩阵相减。
定义十个中间矩阵:
再定义七次矩阵乘法:
最终:
Z = Z 11 Z 12 Z 21 Z 22 = X Y Z = \begin{matrix} Z_{11} & Z_{12} \\ Z_{21} & Z_{22} \end{matrix} = XY Z=Z11Z21Z12Z22=XY
因此:
总共7个 n/2*n/2的矩阵相乘 + 11个n/2*n/2 的矩阵相加 + 7个n/2*n/2 的矩阵相减。
时间复杂度
T ( n ) = 7 ∗ T ( n / 2 ) + O ( n 2 ) T ( n ) = O ( n l o g 2 7 ) = O n 2.81 ) T(n) = 7*T(n/2) + O(n^2) \\ T(n) = O(n^{log_27}) =O{n^{2.81}}) T(n)=7∗T(n/2)+O(n2)T(n)=O(nlog27)=On2.81)