DP常用优化

「算法总结」DP常用优化

referrence:[DP的各种优化(动态规划,决策单调性,斜率优化,带权二分,单调栈,单调队列)- FlashHu]

一.前缀和优化

  当遇到类似:\(f[i] = \sum_{j = k}^{i} g[j]\)的转移时,可以通过预处理出\(g[i]\)的前缀和\(s[i]\),将\(O(n)\)的求和转换为\(O(1)\)的操作。

[HAOI2009]逆序对数列

[HAOI2008]木棍分割 二分答案+dp

P4099 [HEOI2013]SAO 树形dp

二.决策单调性——单调队列优化

  接下来几种优化方法主要是对1d/1d dp的优化,其中xd/yd dp指的是状态数有\(n^x\)种,每个状态的转移有\(n^y\)种的dp。对于1d/1d dp,我们可以写出其转移的通式:\(f[i] = min/max\{f[j] + w(i, j)\}\),其暴力转移是\(O(n^2)\)级别的,我们希望能够通过式子的某些特殊性来优化转移。

  接下来我们会考察三种式子,第一种式子形如:
\[ f[i] = min_{j = i - d}^{i - 1}\{f[j] + w(j) + w(i)\} \]
  我们可以看到这一类式子的特点:

1.对于i的转移,其决策点是一段连续的区间,并且这个区间随着i的右移而单调右移。

2.每次转移的代价\(w(i,j)\)中,i与j的花费相互独立。

  那么,首先考察两个决策\(j\)\(k\)(\(j<k\)),如果对于状态\(i\)有决策​\(k\)优于​\(j\),那么对于​\(\forall i'(i'>i)\)都有\(k\)优于\(j\),即从\(k\)转移一定更优,\(j\)一定不再会成为最优决策,决策是单调的,则我们称这样的方程满足决策单调性。那么,我们只需要维护一个单调队列,维护队列中的决策单增/单减,对于转移\(i\),首先检查队头元素是否还属于合法的转移区间\([i - d, i - 1]\),如果不合法则pop_front()。由于单调队列的单调性,此时的队头就是最优决策,进行转移。接着,更新决策区间,维护单调性,将新的决策点从队尾加入单调队列。当然,当决策不满足单调性时,我们只需要用线段树或st表等数据结构维护决策区间最值即可,这样可以达到\(O(nlogn)\)的复杂度。

[NOI2005]瑰丽华尔兹

[SCOI2010]股票交易

[POI2015]WIL-Wilcze doły

  关于单调队列优化有一个非常经典的算法——单调队列优化多重背包:

  首先我们知道多重背包的方程:\(f[i][j] = max\{f[i -1][j], f[i - 1][j - k * w[i]] + c[i] *k\}\)。我们单独关注后面的部分,多重背包复杂度\(O(nmk)\)就在于最后枚举每件物品的个数,如果用二进制拆分可以将复杂度优化到\(O(nmlogk)\)。但是,如果我们想要进一步提高效率,我们就会用到单调队列优化多重背包。

  首先,我们把式子改写成可以单调队列优化的形式:首先,对于重量为\(w\),价值为\(c\)的物品,令\(j=nw+p\),则原方程可以写成\(f[i][j] = max\{f[i -1][j], f[i - 1][p + k * w[i]] - c[i] *k+ c[i] * n\}, p \in[0,w]\),这个式子已经被调整为单调队列优化的式子了,对于每个物品先枚举mod w后的余数,在单调队列里维护\(f[i - 1][p + k * w[i]] - c[i] *k\)递增即可。

宝物筛选 单调队列优化多重背包

三.决策单调性——斜率优化

  考察的第二种式子形如:
\[ f[i] = min\{f[j] + w(i,j)\} \]
  且要求这一类式子具有以下特点:

1.w(i,j)展开后能得到关于\(i、j\)的乘积项,经过调整能够化成形如\(\frac{y1-y2}{x1-x2}\)的斜率式

2.具有决策单调性

1.几何解释

  那么首先来考虑一个一般性的方程\(f[i] = min\{a[i]\times b[j] + c[i] + d[j]\}(a,b,c,d >0且b单增)\),其中\(a[i]\times b[j]\)就是我们所说的关于\(i,j\)的乘积项。对于状态\(i\)我们考察他的决策,那么我们可以先把方程化为以\(j\)的相关项为自/因变量,即:
\[ -d[j] = a[i]\times b[j] + c[i] - f[i] \]
  我们将这个式子对应回直线的斜截式\(y=kx+b\)\(y=-d[j], x = b[j], k = a[i], b = c[i] -f[i]\),我们希望最小化\(f[i]\),那么也就是要最大化\(b\),由于斜率\(k\)是一定的,那么我们考虑进行线性规划,将每个决策\(j\)对应到座标系上,直线与这些决策点形成的凸包相切时可以取到\(b\)的最值,由于我们要的是\(b\)的最大值且\(k=a[i]>0\),那么直线要和凸包上切,换言之,我们只需要维护这些决策点形成一个下凸包即可。

1573418-20190120123453121-1158252230.png

  接下来考虑如何维护这个上凸包,凸包斜率单调减,那么我们依然可以用单调队列维护,新加入一个决策\(i\)时,先与队尾比较,判断是否满足\(slope(i, q[t]) <slope(q[t], q[t - 1])\),如果满足直接加入,否则,则如下图\(k_{DC} > k_{BC}\),说明此刻\(C\)一定不会出现在之后的最优策略里(不可能成为直线切点),那么我们pop_back()掉队尾即可。

1573418-20190120124433318-1776751514.png

  现在单调队列里维护了决策凸包,接着我们每次转移的时候如何在凸包中选出最优决策呢?如果我们的斜率\(k=a[i]\)是单调的(以单减为例),对于\(i'>i有a[i']<a[i]\)且i的最优决策点\(C\),由于决策单调性\(i'\)的决策一定会是\(C\)往后的决策。或者总图形的角度说,如下图,有\(k_{AB} > k_{BC} > k_{CD} >a[i']\),决策A、B、C一定都不是优的。综上,若\(a[i] < k(q[h], q[h - 1])\)时,决策\(q[h]\)一定不优,pop_front()到\(q[h]\)最优即可,这样暴力移动队首指针可以将复杂度优化到\(O(n)\)

1573418-20190120130347926-1599403440.png

  如果斜率不单调,那么我们可以通过在凸包上二分将复杂度优化成\(O(nlogn)\)。在开始的式子\(f[i] = min\{a[i]\times b[j] + c[i] + d[j]\}(a,b,c,d >0且b单增)\)中有个条件:b单增。这个条件也就是决策点的x单增,使得我们能够用单调队列维护凸包。如果b不单增时,我们则必须通过CDQ分治或者平衡树来维护凸包。

2.代数解释

  最后补充另一种理解斜率优化,判断决策单调性的方法,依然对于式子\(f[i] = min\{a[i]\times b[j] + c[i] + d[j]\}\) ,我们假设有决策\(j,k(j<k)\)\(k\)优于$j,则有:
\[ a[i]\times b[j] + c[i] + d[j] > a[i]\times b[k] + c[i] + d[k] \]

\[ 即:a[i]\times (b[j] - b[k]) > d[k]-d[j] \]

\[ \frac{-d[k] - (-d[j])}{b[k]-b[j]}>a[i] \]

  这样就得到了决策优劣判断的关系式了,具体含义理解应对回前面的几何意义即可。

[HNOI2008]玩具装箱TOY

[CEOI2004]锯木厂选址

[NOI2007]货币兑换 斜优+CDQ分治

高速公路 可持久化数组+斜优/斜优+二分

四.决策单调性——二分数据结构与分治

  当这一类式子中的\(w(i,j)\)已经不像前两种情况下那么独立了,譬如\(\sqrt{\mid a[i]-a[j] \mid}, (a[i] - a[j])^p\)等等。但是,如果转移依然满足决策单调性了话,我们便能够将其优化成$O(nlogn) 。一般有两种策略:​

1.分治

  分治法能够解决的问题一般要求转移形如:
\[ f[i] = min \{g[j] + w(i, j)\} \]
  也就是说\(f[i'](i'>i)\)\(f[i]\)没有依赖,当我们求解一段区间\(f_{[L,R]}\)且我们知道这段区间的转移区间为\([l,r]\),则可以二分出中\(mid\)\(O(n)\)暴力扫\([L, mid)\)求出其最优决策点\(k\),由于决策单调性可知\([L,mid - 1]\)的决策区间一定在\([l, k]\)中,另一半同理。如此就将原问题化成了两个子问题解决,最终实现\(O(nlogn)\)的复杂度。

2.单调队列

当我们遇到的转移形如:
\[ f[i] = min \{f[j] + w(i, j)\} \]
  由于\(f[i'](i'>i)\)\(f[i]\)有依赖,不能够使用分治法。那么我们可以转换思路:之前的所有方法都是考虑某个状态的最优决策,现在我们可以尝试反过来考虑某个决策作为最优决策能够转移到的状态有哪些。我们来看个例子:
  对于某次dp,式子同上,,我们用\(q[]\)表示每个状态的最优决策。最初,只有边界条件\(f[0]\),则此时所有状态都只能从0转移来:

q[]:00000000000000000000000000000000000000

  由于f[1]只能够从f[0]转移,所有我们更新了f[1],继而f[1]有可以成为某些状态的最优决策,更新:

q[]:00111111111111111111111111111111111111

  可见f[2]也只能从f[0]转移,接着上述操作更新:

q[]:00111111111111111111222222222222222222

  f[3]从f[1]转移:

q[]:00111111111111111111222222222223333333

  f[4]从f[2]转移,假设这个f[4]非常优,优过f[2]、f[3]与f[1]的部分,那么我们用4代替原来2,3与1的部分

q[]:00111111111144444444444444444444444444

  我们的整个操作过程大致如此,那我们可以使用单调队列来维护这样一个过程,在队列中储存的元素为一个三元组\((i,l,r)\)表示决策\(i\)是状态\(f_{[l,r]}\)的最优决策,每次从队首取出最优决策,并且作为新的决策更新队尾即可。当然,对于形如分治法的式子,即\(f[i'](i'>i)\)\(f[i]\)无依赖,单调队列的方法同样也能解决。但这个做法同样有缺陷,在对于\(w(i,j)\)较为复杂,无法\(O(1)\)求得时,其复杂度则会下降。

3.决策单调性的判断

  如何判断一个方程式是否具有决策单调性,除了暴力打表,大胆猜测,大眼观察与以外,这里再提供两种方法:

  第一种方法我们在斜率优化部分已经见过,设有\(j<k\)且对于转移\(i\)决策\(j\)优于\(k\),列出不等式,移项化简得到与\(i\)有关的不等关系,结合单调性,正负性等进行判断。

  其次,有些特殊的式子是一定具有单调性的,比较常见的是四边形不等式,四边形不等式指的是对于\(a<b<c<d\)\(w(a, d) + w(b, c) >= w(a, c) + w(b, d)\)的一类式子。在遇到这类式子时可以直接套用决策单调性的套路。四边形不等式同样可以用于二维dp的优化,在此不叙。

[POI2011]Lightning Conductor

Yet Another Minimization Problem 分治

[NOI2009]诗人小G 四边形不等式

五.two pointer优化

  在某些转移中(例如枚举左右端点),方程中的两维i与j存在类似于\(s[i]+s[j]<val\)的限制条件,并且我们知道\(s[i]\)\(s[j]\)都是单增的,那么当我们左移i指针时\(s[i]\uparrow,s[j]\downarrow\),则j指针只能够向右移动。这样,我们可以通过调整指针移动的顺序和方向将二维的枚举优化成一维。

[NOI2011]Noi嘉年华

[AH2017/HNOI2017]大佬 dp+bfs

[ZJOI2013]蚂蚁寻路

六.数据结构优化dp

​ 除了以上的dp,某些dp可能并不像上面这些式子这么有迹可循,当然,这些dp在转移时也许会有譬如区间求和,求最值等子问题。那么我们可以利用线段树,树状数组,堆等数据结构进行相关维护。这一部分操作相对灵活,因题而异,故不展开。

[Usaco2005 Dec]Cleaning Shifts 清理牛棚 线段树+dp

CF833B The Bakery 线段树+dp

[SCOI2014]方伯伯的玉米田 二维BIT+dp

[ZJOI2010]基站选址 线段树+dp

七.带权二分/WQS分治/dp凸优化

1.带权二分

​ 对于一类问题要求取正好C个物品且同时最小化费用,我们可以使用带权二分来解决,首先我们先看一道经典例题:

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。

V<=50000,E<=100000,w为[1,100]中的正整数

P2619 [国家集训队2]Tree I

​ 我们所能做到的仅仅是求出一个给定图的最小生成树,也就是说我们很容易能够满足最小权的要求,然而黑白边的限制要如何处理呢?假设我们已经求出了图的最小生成树,其中有\(x(x<need)\)条白边,那么我们所需要做的事情就是让我们的算法能够更优先选若干白边。于是我们可以给每条白边加一个负权\(cost\),也就是说相应地减少白边的边权,使得白边更容易被选,求出答案后再加回这些剪掉的权即可。同样,对于\(x>need\)则可以通过加正权来实现少选白边,我们知道加权与白边数量是负相关的,那么我们在一个范围(如\([-100, 100]\))间二分边权即可。

​ 以上这个,通过二分权值,给操作加权来控制操作数量的方法就是带权二分/wqs分治。

​ 记\(g(x)\)为正好选\(x\)个时的最优解,能够带权二分的问题一般都有以下性质:

1.\(g(x)\)的最值可以较方便地求出

2.\(g(x)\)为凸函数

​ 带权二分的正确性可以通过直线切凸包的方式证明,在此不叙。带权二分一般需要注意二分边界以及边界移动方向的问题。一般来说边界可以直接选择\([-inf, inf]\),当然也可以结合凸包的斜率算出边界。其次,当当前最优解的转移次数大于目标次数时,说明需要增大每次选择的附加费用以达到减小选择次数的目的,反之亦然。在此基础上进行二分即可。

2.dp凸优化

​ 应用于DP上的带权二分即称作dp凸优化,最常见用于优化2d/1d dp。譬如要求将某个序列分成正好m段,且最大化费用,式子可表示成:
\[ f[i][j] = max\{f[k][j - 1] + w(k + 1, i)\} \]
​ 最暴力的做法时间复杂度高达\(O(n^2m)\),当然结合前面的几种优化,对于每个\(j\),我们可以将这个1d/1d dp优化到\(O(nlogn)\)甚至\(O(n)\)的复杂度,那么总的复杂度仍然高达\(O(nmlogn)\)\(O(nm)\),在\(n,m\)较大时仍然不够优。此刻,我们就可以应用带权二分将最外层的m优化成对数级别。

​ 如果在没有C的限制,即可以分成任意多段时,我们可以\(O(nlogn)\)\(O(n)\)求出此刻的最优值,并且\(g(x)\)满足上文提到的条件,那么我们就可以在外层二分每次分段时的额外费用,进行dp,同时记录分段数,即:
\[ f[i] = max\{f[j] + w(j + 1, i) + cost\}\qquad g[i] = g[j_{best}] + 1 \]
​ 最后,将\(g(n)\)\(C\)比较,继续二分即可,最终的答案要减去\(cost * C\)

[SDOI2016]征途

Ciel and Gondolas

[八省联考2018]林克卡特树lct 树dp+wqs

转载于:https://www.cnblogs.com/grannychan/p/10295567.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单调队列优化DP是一种常用优化方法,可以将时间复杂度从 $O(n^2)$ 降低到 $O(n)$ 或者 $O(n \log n)$。以下是一道利用单调队列优化DP的典型题目: 题目描述: 给定一个长度为 $n$ 的序列 $a_i$,定义 $f(i)$ 为 $a_i$ 到 $a_n$ 中的最小值,即 $f(i) = \min\limits_{j=i}^n a_j$。现在定义 $g(i)$ 为满足 $f(j) \ge a_i$ 的最小下标 $j$,即 $g(i) = \min\{j \mid j > i, f(j) \ge a_i\}$。如果不存在这样的下标 $j$,则 $g(i) = n+1$。 现在请你计算出 $1 \le i \le n$ 的所有 $g(i)$ 的值。 输入格式: 第一行包含一个整数 $n$。 第二行包含 $n$ 个整数 $a_1,a_2,\cdots,a_n$。 输出格式: 输出 $n$ 行,第 $i$ 行输出 $g(i)$ 的值。 输入样例: 5 3 1 2 4 5 输出样例: 2 5 5 5 6 解题思路: 设 $dp(i)$ 表示 $g(i)$,那么 $dp(i)$ 与 $dp(i+1)$ 的转移关系可以表示为: $$dp(i)=\begin{cases}i+1, &\text{if}\ f(i+1)\ge a_i \\dp(i+1), &\text{else}\end{cases}$$ 这个转移方程可以使用暴力 DP 解决,时间复杂度为 $O(n^2)$。但是,我们可以使用单调队列优化 DP,将时间复杂度降为 $O(n)$。 我们定义一个单调队列 $q$,存储下标。队列 $q$ 中的元素满足: - 队列中的元素是单调递减的,即 $q_1 < q_2 < \cdots < q_k$; - 对于任意的 $i\in [1,k]$,有 $f(q_i) \ge f(q_{i+1})$。 队列 $q$ 的作用是维护一个长度为 $k$ 的区间 $[i+1,q_k]$,满足这个区间中的所有 $j$ 都满足 $f(j) < f(i+1)$。 根据定义,当我们要求 $dp(i)$ 时,只需要查找队列 $q$ 中第一个满足 $f(q_j) \ge a_i$ 的位置 $q_j$,那么 $g(i) = q_j$,如果队列 $q$ 中不存在这样的位置,则 $g(i) = n+1$。 那么如何维护单调队列 $q$ 呢?我们可以在每次 DP 的过程中,将 $i$ 加入队尾。然后判断队首元素 $q_1$ 是否满足 $f(q_1) \ge a_i$,如果满足则弹出队首元素,直到队首元素不满足条件为止。 由于每个元素最多被加入队列一次,并且最多被弹出一次,因此时间复杂度为 $O(n)$。具体实现细节可以参考下面的代码实现:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值