斜率优化Convex Hull Trick

本文通过几个具体案例,深入浅出地介绍了斜率优化动态规划的基本思想与实现方法,包括如何利用斜率来优化DP转移过程,减少不必要的计算,提高算法效率。

斜率优化

一、简单DP

首先从一道简单题引入。

[IOI2002]任务安排

Description

N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数ci。请确定一个分组方案,使得总费用最小。

例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。

Input

第一行是N(1<=N<=5000)。

第二行是S(0<=S<=50)。

下面N行每行有一对数,分别为Ti和ci,均为不大于100的正整数,表示第i个任务单独完成所需的时间是Ti及其费用系数ci。

Output

一个数,最小的总费用。

Sample Input

5

1

1 3

3 2

4 3

2 3

1 4

Sample Output

153

Solution

“光着身子”的动态规划。

设F[i,j]表示划分前i个任务为j批的最小费用。

F[i,j]=min(F[k,j-1]+(s*j+\sum_{q=k+1}^{i}t[q] ))*(\sum_{q=k+1}^{i}c[q]))

设T[i]=\sum_{j=1}^{i}t[j]                 C[i]=\sum_{j=1}^{i}c[i]

F[i,j]=min(F[k,j-1]+(s*j+T[i]-T[k] ))*(C[i]-C[k]))

(2D/1D),时间复杂度:O(n^3),空间复杂度:O(n^2)

这显然是不够的。

 

实际上我们不一定要知道之前划分了多少批任务,只需要记录F[i]表示划分前i个任务的最小费用。

F[i]=min(F[j]+(s+T[i])*(C[i]-C[j]))+s*(C[n]-C[i])

这样就写出了(1D/1D)动态规划方程。

二、简单题2

将上题的n<=5000改为n<=300000。

Solution

这就是本文的重点所在了。

在上文中的(1D/1D)动态规划中,尚有两种简单的优化思路:

  1. 优化状态:将这样一个动态规划转化为贪心或数学题。
  2. 优化转移:优化转移的时间(主要为优化重复或不必要的转移)。

显然,优化状态很难达成,那么我们考虑优化转移。

我们可以发现在转移的过程中有很多的不必要的转移:

考虑一下两个转移:

F[i]=min(F[j]+(s+T[i])*(C[i]-C[j]))+s*(C[n]-C[i])

F[i]=min(F[k]+(s+T[i])*(C[i]-C[k]))+s*(C[n]-C[i])

(j<k<i)

在此题中,若 min(F[j]+(s+T[i])*(C[i]-C[j]))\geqslant min(F[k]+(s+T[i])*(C[i]-C[k]))

那么转移 j 就是不必要的。

也就是说,若有(i'>i)且选取转移状态的集合\begin{Bmatrix} i':& j1, &j2, &... &j\alpha \end{Bmatrix}\subseteq \begin{Bmatrix} i:& j1, &j2, &... &j\beta \end{Bmatrix}

并且存在,后面的转移比前面的优,则前面的转移就是无意义的,也就是对最终答案无贡献的了。

 

考虑本题,如何判断转移状态是否是有用的呢?

F[j]+(s+T[i])*(C[i]-C[j])\geqslant F[k]+(s+T[i])*(C[i]-C[k])

\therefore F[j]-s*c[j]-T[i]*c[j]\geqslant F[k]-s*c[k]-T[i]*c[k]

\therefore F[j]-f[k]+s*(c[k]-c[j])\geqslant T[i]*(c[j]-c[k])

\therefore \frac{F[j]-f[k]+s*(c[k]-c[j]))}{(c[j]-c[k]) }\leqslant T[i]

也就是说,如果 \small \frac{F[j]-f[k]+s*(c[k]-c[j]))}{(c[j]-c[k]) }\leqslant T[i]

那么后面的转移比前面的优,否则前面的转移比后面的优。

这样的\small \frac{F[j]-f[k]+s*(c[k]-c[j]))}{(c[j]-c[k]) },即是用斜率的形式,表现了转移之间的不必要的关系。

 

进一步,我们发现:

\small \frac{F[j]-f[k]+s*(c[k]-c[j]))}{(c[j]-c[k]) }\leqslant T[i]\leqslant T[i+1]

也就是说,如果当前存在后面的转移比前面的优,那么这个转移在之后的状态中都不会成为最有转移!

根据这个性质,我们选择用单调队列维护一个两点间斜率单调递增的点集队列。

设:

\small g(x1,x2)=\frac{F[x1]-f[x2]+s*(c[x2]-c[x1]))}{(c[x1]-c[x2]) }

  1. 若有  \small g(x1,x2)\geqslant g(x2,x3),则\small x2的转移必然不会是最优的,于是能够将它删除。(维护队尾)
  2. 若有\small g(x1,x2)<=T[i],        则x1的转移必然不会是最优的,于是能够将它删除。 (维护队首)

最后的点集队列满足:在队首的点的转移必然是最优的。

于是\small F[i]=F[que[head]]+(s+T[i])*(C[i]-C[que[head]])+s*(C[n]-C[i])

将转移化为O(1)(1D/0D)动态规划完成。

三、简单题3

若   -512<=t[i]<=512 ,也就是说:

\small T[i]\leqslant T[i+1]    不再成立怎么办呢?

我们发现:

  1. 若有  \small g(x1,x2)\geqslant g(x2,x3),则\small x2的转移必然不会是最优的,于是能够将它删除。(维护队尾)

这一性质依然成立,那么这个点集集合还是会组成一个相邻点斜率递增的单调队列。

\small g(x1,x2)\leqslant g(x2,x3)...\leqslant g(x\alpha ,x\alpha+1 )<=T[i]<=g()...   

发现:

  1. 如果\small g(x\alpha ,x\alpha+1 )< T[i],那么\small x\alpha +1优于\small x\alpha
  2. 如果\small g(x\alpha ,x\alpha+1 )> T[i],那么\small x\alpha优于\small x\alpha +1

也就是说,答案一定存在于一个点\small x\alpha,使得g(x\alpha-1,x\alpha ) \leqslant T[i] 且 g(x\alpha,x\alpha +1) \geqslant T[i]

那么\small x\alpha必然最优。

我们可以维护单调队列,只在队尾删除,每次二分查询一个最优点。

这样便是O(nlgn)的斜率优化做法。

 

四、简单题4

一种形式斜率优化的形式:

队列中的点不满足单调性。

BZOJ1492: [NOI2007]货币兑换Cash

题目描述:详见BZOJ。

这一题就是典型的例子。

斜率不单调,那么就必须用cdq分治或splay维护斜率凸包解决斜率优化问题了。

下次有时间再补充。。。

### ConvexHull 算法原理详解 ConvexHull(凸包)是计算几何中的一个基础问题,其目标是找到一个最小的凸多边形,使得给定点集中所有的点都位于该多边形的边界上或内部。凸包算法广泛应用于计算机视觉、图形学、机器人路径规划等领域。 #### 1. 凸包的基本定义 对于一个二维平面上的点集 $ Q $,其凸包是一个最小的凸多边形,满足 $ Q $ 中的每个点都在多边形的边界上或内部。凸包通常用 $ CH(Q) $ 表示,即 Convex Hull of $ Q $ [^2]。 #### 2. 凸包算法的常见类型 凸包的求解算法有很多种,常见的包括: - **Graham 扫描法**:首先选择一个极点(通常是最左下角的点),然后按照其余点相对于该极点的极角排序,最后依次扫描并维护一个栈,确保每一步都保持凸性。 - **Andrew 算法(上下凸包法)**:将点集按 $ x $ 坐标排序,然后分别构造上凸包和下凸包。 - **分治算法**:将点集分成两部分,分别求解凸包,再合并两个凸包。 - **快速凸包算法(QuickHull)**:类似于快速排序的思想,通过递归划分点集并构建凸包。 #### 3. Graham 扫描法的详细原理 Graham 扫描法是求解凸包的经典算法之一,其时间复杂度为 $ O(n \log n) $,主要步骤如下: - **步骤 1:选择基准点** 选择一个极点,通常是点集中 $ y $ 坐标最小的点,如果有多个点具有相同的 $ y $ 坐标,则选择 $ x $ 坐标最小的点。 - **步骤 2:按极角排序** 以基准点为原点,计算其余点相对于基准点的极角,并按极角从小到大排序。 - **步骤 3:扫描并维护凸包** 使用一个栈来维护当前的凸包顶点。每次将新点加入栈时,检查是否满足凸性条件。如果不满足,则弹出栈顶的点,直到满足条件后再将新点压入栈中。 具体来说,假设当前栈中有两个点 $ b $ 和 $ c $,新加入的点为 $ d $。如果向量 $ \vec{bc} $ 到 $ \vec{cd} $ 的逆时针角度小于 180 度,则说明点 $ c $ 不属于凸包,应将其弹出栈。 #### 4. Andrew 算法的详细原理 Andrew 算法通过将点集按 $ x $ 坐标排序后,分别构建上凸包和下凸包,最终合并得到完整的凸包。其时间复杂度也为 $ O(n \log n) $,具体步骤如下: - **步骤 1:按 $ x $ 坐标排序** 将所有点按 $ x $ 坐标从小到大排序,如果 $ x $ 坐标相同,则按 $ y $ 坐标排序。 - **步骤 2:构建下凸包** 从左到右扫描点集,维护一个栈,确保每次加入新点后,凸包的边界仍然保持凸性。 - **步骤 3:构建上凸包** 从右到左扫描点集,同样维护一个栈,确保凸性。 - **步骤 4:合并凸包** 将下凸包和上凸包合并,并去除重复点,得到最终的凸包。 #### 5. 凸包算法的时间复杂度下界 凸包问题的时间复杂度下界为 $ \Omega(n \log n) $,因为任何比较排序问题都可以归约到凸包问题。因此,Graham 扫描法和 Andrew 算法的时间复杂度已经达到理论下界,是高效的算法 。 #### 6. OpenCV 中的 convexHull() 函数 OpenCV 提供了 `convexHull()` 函数用于计算图像中点集的凸包,其函数定义如下: ```cpp void cv::convexHull(InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true) ``` - `points`:输入的二维点集。 - `hull`:输出的凸包顶点。 - `clockwise`:指定凸包的方向,`true` 表示顺时针方向,`false` 表示逆时针方向。 - `returnPoints`:指定输出形式,`true` 表示输出顶点坐标,`false` 表示输出点索引 [^1]。 #### 7. 实际应用中的注意事项 - **输入点集的预处理**:通常需要对点集进行去噪和排序,以提高算法效率。 - **边界情况处理**:当点集中的点共线或重复时,需特别处理以避免算法失败。 - **性能优化**:在大规模点集中,可以采用分治策略或随机化算法提高效率。 ### 示例代码 以下是一个使用 Python 和 OpenCV 计算凸包的简单示例: ```python import cv2 import numpy as np # 读取图像并转为灰度图 image = cv2.imread('input.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 二值化处理 _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 寻找轮廓 contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 计算凸包 for cnt in contours: hull = cv2.convexHull(cnt) # 绘制凸包 cv2.drawContours(image, [hull], 0, (0, 255, 0), 2) # 显示结果 cv2.imshow('Convex Hull', image) cv2.waitKey(0) cv2.destroyAllWindows() ```
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值