[算法模板]动态规划—斜率优化

摘要:
(dp[i]=最小值(dp[j]+(S[i]−S[j]-1-L)^2)该方程取(dp[i]=min(dp[j]+w(i,j)=(S[i]−S[j]-1-L)^2=Q^2)(egin{方程*}egin{分裂},其中w(i+1,
[算法模板]动态规划—斜率优化

本文全文引自Xing-Ling,感谢Xing-Ling提供markdown源码。

【学习笔记】动态规划—各种 ( ext{DP}) 优化

【前言】

第一次写这么长的文章。

写完后感觉对斜优的理解又加深了一些。

斜优通常与决策单调性同时出现。可以说决策单调性是斜率优化的前提。

斜率优化 (DP),顾名思义就是利用斜率相关性质对 (DP) 进行优化。

斜率优化通常可以由两种方式来理解,需要灵活地运用数学上的数形结合,线性规划思想。

对于这样形式的 (dp) 方程:(dp[i]=Min/Max(a[i]∗b[j]+c[j]+d[i])),其中 (b) 严格单调递增。

该方程的关键点在于 (a[i]*b[j]) 这一项,它既有 (i) 又有 (j),于是单调队列优化不再适用,可以尝试使用斜率优化。


一.【决策单调性证明】

这里以 玩具装箱 (toy)([P3195]) 为例,先来证一波决策单调性,方法采用四边形不等式

(S[n]=sum _{i=1}^n (C[i]+1)),用 (dp[i]) 表示装好前 (i) 个的最小花费,则 (dp) 方程为:(dp[i]=min(dp[j]+(S[i]−S[j]-1-L)^2))

很明显,这个方程以 (dp[i]=min(dp[j]+w(i,j))) 的形式呈现,即 (1D/1D) 动态规划方程,其中 (w(i,j)=(S[i]−S[j]-1-L)^2)

(证明:设)(Q=S[i]−S[j]-1-L)

( herefore w(i,j)=(S[i]−S[j]-1-L)^2=Q^2)

(egin{equation*} egin{split} herefore w(i+1,j+1)=&(S[i+1]−S[j+1]-1-L)^2\ =&((S[i]+C[i+1]+1)−(S[j]+C[j+1]+1)-1-L)^2\ =&(Q+C[i+1]-C[j+1])^2 end{split} end{equation*})

(egin{equation*} egin{split} w(i,j+1)=&(S[i]−S[j+1]-1-L)^2\ =&(S[i]−(S[j]+C[j+1]+1)-1-L)^2\ =&(Q-C[j+1]-1)^2 end{split} end{equation*})

(egin{equation*} egin{split} w(i+1,j)=&(S[i+1]−S[j]-1-L)^2\ =&((S[i]+C[i+1]+1)−S[j]-1-L)^2\ =&(Q+C[i+1]+1)^2 end{split} end{equation*})

( herefore w(i,j)+w(i+1,j+1)=2X^2+2C[i+1]X-2C[j+1]X+C[i+1]^2-2C[i+1]C[j+1]+C[j+1]^2)

( herefore w(i+1,j)+w(i,j+1)=2X^2+2C[i+1]X-2C[j+1]X+C[i+1]^2+2C[i+1]+2C[j+1]+C[j+1]^2+2)

( herefore w(i,j)+w(i+1,j+1)-w(i+1,j)+w(i,j+1)=-2(C[i+1]+1)(C[j+1]+1))

(又 ecause C[i],C[j] geqslant 1)

( herefore -2(C[i+1]+1)(C[j+1]+1) leqslant -8)

( herefore w(i,j)+w(i+1,j+1) leqslant w(i+1,j)+w(i,j+1))

四边形不等式成立,所以次题具有决策单调性。

在实战中,通常使用打表的形式来判断是否满足四边形不等式。


二.【理解方式】

还是以 玩具装箱 (toy)([P3195]) 为例,两种斜优的理解方式。

决策方程为:(dp[i]=min(dp[j]+(S[i]−S[j]-1-L)^2))
为方便描述,将 (L) 提前加 (1),再把 (min) 去掉,得到状态转移方程:(dp[i]=dp[j]+(S[i]−S[j]-L)^2)

化简得:(dp[i]=S[i]^2-2S[i]L+dp[j]+(S[j]+L)^2-2S[i]S[j])

1.【代数法】


只含 (L) 的项对于每一个 (i)择优筛选过程都是完全一样的值,只含 (Function(i)) 的项在一次 (i)择优筛选过程中不变,含 (Function(j)) 的项始终在发生变化(只要该项是严格单增)。
以此为划分依据,把同类型的项用括号括起来,
即:(dp[i]=(-2S[i]S[j])+(dp[j]+(S[j]+L)^2)+(S[i]^2-2S[i]L))


【维护一个凸包】

(j_1,j_2)((0 leqslant j_1<j_2<i))(i) 的两个决策点,且满足决策点 (j2) 优于 (j1)
有:((-2S[i]S[j_2])+(dp[j_2]+(S[j_2]+L)^2)+(S[i]^2-2S[i]L) leqslant (-2S[i]S[j_1])+(dp[j_1]+(S[j_1]+L)^2)+(S[i]^2-2S[i]L))

即:((-2S[i]S[j_2])+(dp[j_2]+(S[j_2]+L)^2) leqslant (-2S[i]S[j_1])+(dp[j_1]+(S[j_1]+L)^2))

划重点:此处移项需要遵循的原则是:参变分离。将 (Function(i)) 视作未知量,用 (Function(j)) 来表示出 (Function(i))
移项得:(-2S[i](S[j_2]-S[j_1]) leqslant (dp[j_1]+(S[j_1]+L)^2)-(dp[j_2]+(S[j_2]+L)^2))

(ecause C[j] geqslant 1)
( herefore S[j+1] > S[j])
(又 ecause j_2 > j_1)
( herefore S[j_2]-S[j_1]>0)
( herefore 2S[i] geqslant frac {(dp[j_2]+(S[j_2]+L)^2)-(dp[j_1]+(S[j_1]+L)^2)} {S[j_2]-S[j_1]})

(Y(j)=dp[j]+(S[j]+L)^2,X(j)=S[j])
(2S[i] geqslant frac {Y(j_2)-Y(j_1)} {X(j_2)-X(j_1)})
很明显,等式右边是一个关于点 (P(j_2))(P(j_1)) 的斜率式,其中 (P(j)=(X(j),Y(j))=(S[j],dp[j]+(S[j]+L)^2))

也就是说,如果存在两个决策点 (j_1,j_2) 满足 ((0 leqslant j_1<j_2<i)),使得不等式 (frac {Y(j_2)-Y(j_1)} {X(j_2)-X(j_1)} leqslant 2S[i]) 成立,或者说 使得 (P(j_2),P(j_1)) 两点所形成直线的斜率小于等于 (2S[i]),那么决策点 (j2) 优于 (j1)

划重点:斜优灵活多变,细节麻烦也多,所以尽量将问题模式化。
比如这里的最终公式,尽量化为 (frac {(j)-(j')} {(j)-(j')}) 的形式,而不是 (frac {(j)-(j')} {(j')-(j)}) ,虽然直接做的话,也不会出什么问题,但这样子可以方便理解,方便判断凸包方向等等。

假设有酱紫的三个点 (P(j_1),P(j_2),P(j_2))(k_1,k_2) 为斜率,如下图所示情况:
(_{(一直在想这个“代数法”是不是应该叫“数形结合”更好呢?算啦,不管啦QAQ)})

[算法模板]动态规划—斜率优化第1张

显然有 (k_2 < k_1)。设 (k_0=2S[i]),由上述结论可知:
((a).)(k_1 leqslant k_0),则 (j_2) 优于 (j_1) 。反之,若 (k_1 > k_0),则 (j_1) 优于 (j_2)
((b).)(k_2 leqslant k_0),则 (j_3) 优于 (j_2) 。反之,若 (k_2 > k_0),则 (j_2) 优于 (j_3)

于是这里可以分三种情况来讨论:
((1).)(k_0 < k_2 < k_1)。由 ((a),(b)) 可知:(j_1) 优于 (j_2) 优于 (j_3)
((2).)(k_2 leqslant k_0 < k_1)。由 ((a),(b)) 可知:(j_1)(j_3) 均优于 (j_2)
((3).)(k_2 < k_1 leqslant k_0)。由 ((a),(b)) 可知:(j_3) 优于 (j_2) 优于 (j_1)

可以发现,对于这三种情况,(j_2)始终不是最优解,于是我们可以(j_2) 从候选决策点中踢出去(删除),只留下 (j_1)(j_3),删后的情况如下图所示:

[算法模板]动态规划—斜率优化第2张

我们要对某一个问题的解决方案进行优化改进,无非就是关注两个要点:正确性高效性(大多数时候高效性都体现为单调性)。

酱紫做的正确性是毋庸置疑的,因为在 (j_1)(j_3) 其中必定有一个比 (j_2) 更优,所以删除 (j_2) 对答案没有任何影响。

那么高效性呢?自己在脑子里面 (yy) 一下,在一个坐标系的第一象限(本题中 (X(j))(Y(j))均大于等于 (0),至于为什么这里要说等于,下面会提到)中,有若干个离散的点,任取三点,如果左边斜率大于右边斜率,则形成了上述情况,必定会删点,因而消除这种情况。所以将最后留下来的点首位相连的话,其形成的各个线段斜率从左到右必定是单调递增的(有可能非严格递增,这个问题之后再讨论)。

实际上再图中选取最靠左下面,下面,右下面的点首位相连,就是最后留下来的点了,它们形成了一个下凸包,即凸包(又名凸壳)的下半部分(不严谨的讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点——摘自百度百科)。

维护出的图形如下图所示:

[算法模板]动态规划—斜率优化第3张

可以尝试在凸包围起来的区域内任意取一点,其必定可以在包围圈上找到两个点使得改点可以被删除,如上 (L) 点,它与 (D,E) 两点形成了一个可删点图形。

同理,如果把不等式 (frac {Y(j_2)-Y(j_1)} {X(j_2)-X(j_1)} leqslant k_0[i]) 改为 (frac {Y(j_2)-Y(j_1)} {X(j_2)-X(j_1)} geqslant k_0[i]),那么维护出来的就是一个上凸包

【寻找最优决策点】

还是以 玩具装箱 (toy)([P3195]) 为例,在下凸包点集中总会存在一点,使得它与左邻点形成的斜率小于 (k_0) ,与右邻点形成的斜率大于 (k_0)

例如上图中的 (E) 点,设 (k_4 leqslant k_0 < k_5) 由于凸包上面的斜率呈单增态,那么有:(k_1 < k_2 < k_3 < k_4 leqslant k_0 < k_5 < k_6 < k_7),所以决策点 (E) 优于其他所有点,即 (E) 就是 (dp[i])最优决策点
如果暴力查找的话,就是从第一个点开始向后扫描,找到第一个斜率大于 (k_0) 的线段,其左端点即为最优决策点,由于斜率具有单调性,可以二分得到这个点。

2.【线性规划】

强烈推荐用线性规划思想来做题,因为图形的变幻更直观,更直观,更重要的是:在面对一些某某变量不满单调性时,通过图形可以做出判断迅速并改变策略(如使用二分等等),而用代数法的话,很可能会 (WA) 上一整天都找不到问题所在。

但是遇到一些复杂的毒瘤(dp) 方程就得靠代数法了(比如有巨毒的国王饮水记 ([NOI2016])([P1721]))。

【高中数学知识】

先回顾一下 (dp) 方程:(dp[i]=S[i]^2-2S[i]L+dp[j]+(S[j]+L)^2-2S[i]S[j])

对其进行移项变化。划重点:移项要遵循的原则是:把含有 (function(i)*function(j)) 的表达式看作斜率 (k_0) 乘以未知数 (x),含有 (dp[i]) 的项必须要在 (b) 的表达式中,(y) 的表达式中必须含有 (function(j))。如果未知数 (x) 的表达式非递增,那么必须要通过等式两边同乘 (-1) 的方法使其变为单增的表达式。得到一个形如 (kx+b=y) 的表达式。

至于为什么说 (x) 的表达式一定要单增,(Hmm...) 其实是为了让一些较简单的问题模式化,不易出错,如果你非要单减,可以尝试倒叙枚举,至于是否正确,具体实现需要注意的玄学问题等等,因为觉得太麻烦没有试过,我也不清楚会发生什么。

例如此题,原 (dp) 方程可化为:
((2S[i])S[j]+(dp[i]-S[i]^2+2S[i]L)=(dp[j]+(S[j]+L)^2))

其中 (k_i=2S[i],)(x_i=S[j],)(b_i=dp[i]-S[i]^2+2S[i]L,)(y_i=dp[j]+(S[j]+L)^2)

其实也可以化为:
((2S[i])(S[j]+L)+(dp[i]-S[i]^2)=(dp[j]+(S[j]+L)^2))
其中 (k_i=2S[i],)(x_i=S[j]+L,)(b_i=dp[i]-S[i]^2,)(y_i=dp[j]+(S[j]+L)^2)

还可以化为 (...)

(...)

只要满足上述移项原则,对答案是没有任何影响的。

这里以第一种形式为例,先画出草图:

[算法模板]动态规划—斜率优化第4张

我们的目的是求出一个最优决策点(j) 使得 (dp[i]) 最小,又因为 (b[i]=dp[i]-S[i]^2) ,所以就是要找到某个点使这条直线经过它时 (b) 最小,即是高中数学上的线性规划问题。

【寻找最优决策点】

[算法模板]动态规划—斜率优化第5张

如图所示,点 (E) 即为最优决策点。显然,这个使得 (b) 最小的最优决策点位于下凸包点集中。


三.【维护凸包】

而实际上只要让维护的凸包方向相同,两种思路的代码可以做到一模一样。

用单调队列维护凸包上的点集,操作分三步走:
((1).) 进行择优筛选时,在队列中二分找到最优决策点(j)
((2).)最优决策点(j) 更新 (dp[i])
((3).) 判断当队尾的点与点 (i) 形成可删点图形时,出队直至无法再删点,然后将 (i) 加入队列。

操作 ((3)) 有两种判断方法(以下凸包为例),一种是 (slope(Q[t-1],Q[t]) geqslant slope(Q[t],i)),另一种是 (slope(Q[t-1],Q[t]) geqslant slope(Q[t-1],i)),两种写法都表示出现了可以删去点 (Q[t]) 的情况。
其中 (Q) 是维护凸包点集的队列。

该题时间复杂度为 (O(nlogn)),似乎还不太优秀。


四.【再优化】

运用决策单调性。

由于最优决策点递增,可以用单调队列来维护,操作 ((2),(3)) 不需要改动,操作 ((1)) 改为:判断当队首的第一根线段斜率小于等于 (k_0[i]) 时,出队直至斜率大于 (k_0[i]),此时的队首即为最优决策点

其时间复杂度为 (O(n))


五.【再证决策单调性】

一样的,两种思路。

先观察 (k_0[i]) 的表达式:(k_0[i]=2S[i]) ,很明显 (k_0) 呈单增态。

1.【代数法】

(k_0[i]) 递增就说明我们找到的第一个斜率大于 (k_0[i]) 的线段在不断地向后移,也就是说,如果我们找到了某一个最优决策点 (j),那么在下一次决策中,最优决策点 (j') 必定在 (j) 的后面。

决策单调性得证。

2.【线性规划】

画出草图:

[算法模板]动态规划—斜率优化第6张

直线 (Line_i) 的斜率 (k_0[i]) 递增,

由图可知最优决策点在递增。

决策单调性得证。


六.【Code】

玩具装箱 (toy)([P3195])

这道题 (...) 太水了 (...) 我一开始 (L) 忘了加 (1) 居然还过了 (...)

#include<cstring>
#include<cstdio>
#define LL long long
#define Re register LL
const int N=5e4+5;
LL i,j,n,L,h=1,t=0,Q[N],S[N],dp[N];
//S[n]=∑C[i]+1, dp[i]=min(dp[j]+(S[i]-(S[j]+L+1))^2),++L 
//dp[i]=S[i]^2-2*S[i]*L+dp[j]+(S[j]+L)^2-2S[i]*S[j]
//(2*S[i]) * S[j] + (dp[i]-S[i]^2+2S[i]L)=(dp[j]+(S[j]+L)^2)
//   k     *  x   +           b          =        y
inline LL min(Re a,Re b){return a<b?a:b;}
inline LL X(Re j){return S[j];}
inline LL Y(Re j){return dp[j]+(S[j]+L)*(S[j]+L);}
inline long double slope(Re i,Re j){return (long double)(Y(j)-Y(i))/(X(j)-X(i));}//记得开long double 
int main(){
    scanf("%lld%lld",&n,&L);++L; 
    for(i=1;i<=n;S[i]+=S[i-1]+1,++i)scanf("%lld",&S[i]);
    Q[++t]=0;//重中之重 
    for(i=1;i<=n;++i){
        while(h<t&&slope(Q[h],Q[h+1])<=2*S[i])++h;//至少要有两个元素 h<t。出队判断时尽量加上等号 
        dp[i]=dp[j=Q[h]]+(S[i]-S[j]-L)*(S[i]-S[j]-L);
        while(h<t&&slope(Q[t-1],Q[t])>=slope(Q[t-1],i))--t;//至少要有两个元素 h<t。入队判断时尽量加上等号 
        Q[++t]=i;
    }
    printf("%lld",dp[n]);
}

七.【各种玄学问题】

前方高能预警,画好草图,盯着代码,准备!冲!

((1).) 写出 (dp) 方程后,要先判断能不能使用斜优,即是否存在 (function(i)*function(j)) 的项。

((2).) 通过大小于符号或者 (b)(dp[i]) 的符号结合题目要求 ((min/max)) 判断是上凸包还是下凸包,否则死活求不出答案。

((3).)单调性出锅 (1))将方程变为 (frac {Y(j_2)-Y(j_1)} {X(j_2)-X(j_1)} leqslant k_0[i]) 或者 (frac {Y(j_2)-Y(j_1)} {X(j_2)-X(j_1)} geqslant k_0[i]) 或者 (kx+b=y) 的形式,变化要遵循之前提到的原则,尤其是 (X) 表达式的单调性,结合图形会更好理解。如果 (X) 不单调,那么需要用到 (CDQ) 分治或者平衡树维护凸包。

((4).)单调性出锅 (2))注意是否具有决策单调性,有时候打表只能得到片面的情况。当斜率不是单调递增时该怎么办?由于我们不知道什么时候会在什么地方取得最优决策点,所以必须要保留整个凸包以确保决策有完整的选择空间,查找答案就只能二分了,而不能直接取队首,比如这道题 任务安排 (3)([Loj10186])([BZOJ2726])就不满足,其证明放在后面。

((5).)单调性出锅 (3))当 (X) 非严格递增时,那么在求斜率时可能会出现 (X(j_1)==X(j_2)) 的情况,最好是写成这样的形式:(return Y(j) geqslant Y(i)?inf:-inf),而不要直接 (return)(inf) 或者 (-inf),在某些题中情况较复杂,如果不小心画错了图,返回了一个错误的极值就完了,而且这种错误只用简单数据还很难查出来。

((6).) 注意比较 (k_0[i])(slope(j_1,j_2)) 要写规范,要用右边的点减去左边的点进行计算,如果用的代数法理解,写出了 ((X(j_2)-X(j_1))*k_0 leqslant Y(j_2)-Y(j_1))((X(j_2)-X(j_1))*k_0 geqslant Y(j_2)-Y(j_1)),而恰巧 (j_1,j_2) 又写反了,会出现等式两边同除了负数却没变号的情况。当然也可以用 (k_0)(frac {Y(j_2)-Y(j_1)}{X(j_2)-X(j_1)}) 比较就不会出现这种问题 。

((7).) 队列初始化要塞入一个点 (P(0)),还是以 玩具装箱 (toy)([P3195]) 为例,塞入 (P(S[0],dp[0]+(S[0]+L)^2))(P(0,0)),其代表的决策点为 (0)

((8).) 手写队列得初始化是 (h=1,t=0),由于塞了初始点导致 (t)(1),所以在一些题解中可以看到 (h=t=1) 甚至是 (h=t=0,)(h=t=2) 的写法,都是等价的。

((9).) 手写队列判断是否为空是 ({h leqslant t}),而出入队判断时都需要有至少 (2) 两个元素才能进行操作。所以应是 ({h<t})

((10).) 计算斜率可能会因为向下取整而出现误差,所以 (slope) 函数最好设为 (long)(double) 类型(据说范围有 (1.2*10^{4932}) 辣么大)。

((11).) 有可能会有一部分的 (dp) 初始值无法转移过来,需要手动提前弄一下,例如 摆渡车 ([P5017])

((12).) 在比较两个斜率时,尽量写上等于,即 (“leqslant” , “geqslant”) 而不是 (“<”,“>”)。这样写对于去重有奇效(有重点时会导致斜率分母出锅),但不要以为这样就可以完全去重,因为要考虑的情况非常复杂,所以还是应该加上 ((5)) 中提到的特判,万无一失。


八.【补充内容】

(4).单调性出锅 (2)

任务安排 (1)([Loj10184])([P2365])

任务安排 (2)([LO10185])[(Poj1180)]

任务安排 (3)([Loj10186])([Bzoj2726])

【题目描述】

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

例如:(N=5,)(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)

相关数据:

(T1:)(1 leqslant N leqslant 5000, 0 leqslant S leqslant 50,1 leqslant T_i, F_i leqslant 100)

(T2:)(1 leqslant N leqslant 10000, 0 leqslant S leqslant 50,1 leqslant T_i, F_i leqslant 100)

(T3:)(0 leqslant S,F_i leqslant 512,|T_i| leqslant 512)

【T1】

(ST[i]=sum_{k=1}^i T[i],SF[i]=sum_{k=1}^i F[i])

(dp) 方程很简单:(dp[p][i]=min(dp[p-1][j]+(ST[i]+p*S)(SF[i]-SF[j]))),但是 (O(n^3)) 的时间复杂度连 (T1) 都过不了。

由于不知道每一次分段之前已经分了多少,所以需要用一维空间和一层循环来表示这个信息,从而知道 (S) 需要乘以多少。

那么可以反过来,用一种名为费用提前计算的经典思想来进行优化,每分出一批任务,那么对于这之后的每一个任务都需要多出一个 (S) 的时间,所以可以直接计算 (S) 对后面的影响。
即:(dp[i]=min(dp[j]+ST[i](SF[i]-SF[j])+S(SF[n]-SF[j])))
压成了 (O(n^2)) 后,(T1) 就可以 (AC) 了,但它还可以继续优化。

【T2】

先转化为斜率式看看?
((S+ST[i]) * SF[j] + (dp[i]-ST[i]*SF[i]-S*SF[i]) = (dp[j]))
其中 (k=S+ST[i],)(x=SF[j],)(b=dp[i]-ST[i]*SF[i]-S*SF[i],)(y=dp[j])

决策点要使得 (dp[i]) 尽量小,(S+ST[i])(SF[j]) 都严格单增

所以维护一个下凸包即可。

时间复杂度为 (O(n))

【Code】

#include<cstring>
#include<cstdio>
#define LL long long
#define Re register LL
const int N=1e4+5;
LL i,j,n,h=1,t=0,S,Q[N],ST[N],SF[N],dp[N];
//dp[p][i]=min(dp[p-1][j]+(ST[i]+S*p)*(SF[i]-SF[j]));
//dp[i]=dp[j]+ST[i]*(SF[i]-SF[j])+S*(SF[n]-SF[j]);
//(S+ST[i]) * SF[j] + (dp[i]-ST[i]*SF[i]-S*SF[i]) = (dp[j])
//    k     *   x   +              b              = y
inline LL min(Re a,Re b){return a<b?a:b;}
inline LL X(Re j){return SF[j];}
inline LL Y(Re j){return dp[j];}
inline long double slope(Re i,Re j){return (long double)(Y(j)-Y(i))/(X(j)-X(i));}
int main(){
    scanf("%lld%lld",&n,&S);
    for(i=1;i<=n;ST[i]+=ST[i-1],SF[i]+=SF[i-1],++i)scanf("%lld%lld",&ST[i],&SF[i]);
    Q[++t]=0;
    for(i=1;i<=n;++i){
        while(h<t&&slope(Q[h],Q[h+1])<(S+ST[i]))++h;
        dp[i]=dp[j=Q[h]]+ST[i]*(SF[i]-SF[j])+S*(SF[n]-SF[j]);
        while(h<t&&slope(Q[t-1],Q[t])>slope(Q[t-1],i))--t;
        Q[++t]=i;
    }
    printf("%lld",dp[n]);
}

【T3】

(F_i) 可等于 (0)(X()(SF[i])) 非严格递增,所以需要特判 (X(i)==X(j)) 的情况。

(T_i) 可小于 (0)(k_0[i]()(S+ST[i])) 无单调性,所以不具有决策单调性,其证明如下:

(dp) 方程显然为 (dp[i]=dp[j]+w(i,j)) 的形式,其中 (w(i,j)=ST[i](SF[i]-SF[j])+S(SF[n]-SF[j]))

(证明:设)(Q=S(SF[n]-SF[j]))

( herefore w(i,j)=ST[i](SF[i]-SF[j])+Q)

(egin{equation*} egin{split} herefore w(i+1,j+1)=&ST[i+1]SF[i+1]-ST[i+1]SF[j+1]+S(SF[n]-SF[j+1])\ =&ST[i+1]SF[i+1]-SF[j+1]*(ST[i]+T[i+1])+Q-S*F[j+1]\ end{split} end{equation*})

(egin{equation*} egin{split} w(i,j+1)=&ST[i](SF[i]-SF[j+1])+Q-S*F[j+1]\ =&ST[i]SF[i]-ST[i]SF[j+1]+Q-S*F[j+1]\ end{split} end{equation*})

(egin{equation*} egin{split} w(i+1,j)=&ST[i+1](SF[i+1]-SF[j])+Q\ =&ST[i+1]SF[i+1]-ST[i+1]SF[j]+Q\ =&ST[i+1]SF[i+1]-ST[i]SF[j]-T[i+1]SF[j]+Q end{split} end{equation*})

( herefore w(i,j)+w(i+1,j+1)=ST[i](SF[i]-SF[j])+ST[i+1]SF[i+1]-SF[j+1](ST[i]+T[i+1])+2Q-S*F[j+1])

( herefore w(i+1,j)+w(i,j+1)=ST[i]SF[i]-ST[i]SF[j+1]+ST[i+1]SF[i+1]-ST[i]SF[j]-T[i+1]SF[j]+2Q-S*F[j+1])

( herefore w(i,j)+w(i+1,j+1)-w(i+1,j)+w(i,j+1)=-F[j+1]*T[i+1])

(又 ecause 0 leqslant F_i leqslant 512,-512 leqslant T_i leqslant 512)

( herefore 当 T_i leqslant 0 时,w(i,j)+w(i+1,j+1) geqslant w(i+1,j)+w(i,j+1))

(当 T_i geqslant 0 时,w(i,j)+w(i+1,j+1) leqslant w(i+1,j)+w(i,j+1))

四边形不等式不一定成立,所以次题不具有决策单调性。

但并不是说斜优就没法用了,只需在队列中二分最优决策点即可。

时间复杂度为 (O(nlogn))

【Code】

#include<cstring>
#include<cstdio>
#define LL long long
#define Re register LL
const int N=3e5+5;
LL i,j,n,h=1,t=0,S,Q[N],ST[N],SF[N],dp[N];
//dp[p][i]=min(dp[p-1][j]+(ST[i]+S*p)*(SF[i]-SF[j]));
//dp[i]=dp[j]+ST[i]*(SF[i]-SF[j])+S*(SF[n]-SF[j]);
//(S+ST[i]) * SF[j] + (dp[i]-ST[i]*SF[i]-S*SF[i]) = (dp[j])
//    k     *   x   +              b              = y
//ti可小于0,所以ST[i]非递增,只可二分
//fi可等于0,所以SF[i](X)非严格递增,因此需要特判X(i)==X(j)的情况 
inline LL min(Re a,Re b){return a<b?a:b;}
inline LL X(Re j){return SF[j];}
inline LL Y(Re j){return dp[j];}
inline long double slope(Re i,Re j){return X(j)==X(i)?(Y(j)>=Y(i)?1e18:-1e18):(long double)(Y(j)-Y(i))/(X(j)-X(i));
}//由于需要二分查找,多了一些限制:队列里不能有在同一位置的点,返回inf还是-inf都影响着是否删除重点,平时不可不管,二分必须注意返回值
inline LL sakura(Re k){
    if(h==t)return Q[h];
    Re l=h,r=t;
    while(l<r){
    	Re mid=l+r>>1,i=Q[mid],j=Q[mid+1];
    	if(slope(i,j)<k)l=mid+1;
//      if( (Y(j) - Y(i)) < k * (X(j) - X(i)) )l=mid+1;//注意是(j)-(i)因为Q[mid+1]>Q[mid]s即j>i即SF[j]>SF[i]即X(j)>X(i),如果是(i)-(j)的话乘过去要变号
    	else r=mid;
    }
    return Q[l];
}
int main(){
    scanf("%lld%lld",&n,&S);
    for(i=1;i<=n;ST[i]+=ST[i-1],SF[i]+=SF[i-1],++i)scanf("%lld%lld",&ST[i],&SF[i]);
    Q[++t]=0;
    for(i=1;i<=n;++i){
    	j=sakura(S+ST[i]);
        dp[i]=dp[j]+ST[i]*(SF[i]-SF[j])+S*(SF[n]-SF[j]);
        while(h<t&&slope(Q[t-1],Q[t])>=slope(Q[t-1],i))--t;//此处取等号作用出现,如果不取等,会WA第12个点
        Q[++t]=i;
    }
    printf("%lld",dp[n]);
}

(11).预处理 DP 初始值

摆渡车 ([P5017])

这是去年 (noip) 普及组的题(雾)。

【Code】

#include<cstdio>
#define Re register int
const int N=4e6+105;
int i,j,n,m,h=1,t=0,T,ti,ans=2e9,G[N],S[N],Q[N],dp[N];
inline int min(Re a,Re b){return a<b?a:b;}
//                 i
//dp[i]=min(dp[j]+ ∑(i-T[k]))
//                 k=j+1
//dp[i]=dp[j]+(G[i]-G[j])*i-(S[i]-S[j])
//(i) * G[j] + (dp[i]+S[j]-i*G[i]) = (dp[j]+S[j])
// k  *  x   +          b          =      y
inline int X(Re j){return G[j];}
inline int Y(Re j){return dp[j]+S[j];}
inline long double slope(Re i,Re j){return X(i)==X(j)?(Y(j)>Y(i)?2e9:-2e9):(long double)(Y(j)-Y(i))/(long double)(X(j)-X(i));}
int main(){
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;T=-min(-T,-ti),++G[ti],S[ti]+=ti,++i)scanf("%d",&ti);
    for(i=1;i<T+m;++i)G[i]+=G[i-1],S[i]+=S[i-1];
    for(i=0;i<m;++i)dp[i]=G[i]*i-S[i];//提前处理dp初始值 
    Q[++t]=0;
    for(i=m;i<T+m;++i){
    	while(h<t&&slope(Q[h],Q[h+1])<=i)++h;
        dp[i]=dp[j=Q[h]]+(G[i]-G[j])*i-S[i]+S[j];
       	while(h<t&&slope(Q[t-1],Q[t])>=slope(Q[t-1],i-m+1))--t;
        Q[++t]=i-m+1;//(j+1)+m<=i
    }
    for(i=T;i<T+m;++i)ans=min(ans,dp[i]);
    printf("%d",ans);
}

九.【题外话】

斜优实在是恐怖,光是单调性出锅所引发的一系列问题就搞得我差点原地爆炸。

但不得不说,斜优大大提升了我推 (dp) 方程和数学证明的能力。。。

斜优 (NB)(!)

(最近斜优的题写了有七八道,而且每写一道就会踩上一个雷(TAT),所以感悟会这么多,篇幅大也是理所当然的啦QAQ)


十.【例题】

【简单题】

【高档题】


【参考文献】

(本文部分内容摘自以下文章)

[算法模板]动态规划—斜率优化第7张

免责声明:文章转载自《[算法模板]动态规划—斜率优化》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇RTMP,RTMPT,RTMPS,RTMPE,RTMPTE协议的介绍windows下C语言编程获取磁盘(分区)使用情况下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Opencv——相机标定

相机标定的目的:获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的选择和平移矩阵),内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。 相机标定的输入:标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上)。 相机标定的输出:摄像机的内参、外参系数。 标定流程 1. 准备标定...

Android 一种非常好用的Android屏幕适配

前言 网上关于屏幕适配的文章已经铺天盖地了,为什么我还要讲?因为网上现在基本都是使用px适配,即每种屏幕分辨率的设备需要定义一套dimens.xml文件。再加上有些手机还有虚拟按键(例如华为),这样就还需要每个有虚拟按键的设备加多一套dimens.xml文件,再加上平板那些你会发现dimens.xml文件所占的体积已经超过2M了!这绝对不是我们想要的。...

【笔记】博弈论DP基础——简单记忆化的二人博弈类型和sg函数的组合博弈类型

例题 A , B进行游戏。A先开始,轮流将n减去{2,3,4,5,6}中的一个数,谁最后无法进行减法了,就输了。给定n。A,B都采用最优策略,问A是否会赢。 状态 设f[i]表示当前的数是i的时候,对于当前的先手来说是否会赢f[i]=true,则赢f[i]=false,则输 转移 当先手A操作一次后,问题转移为了对于当前先手B,对(n-i)进行操作 必胜转...

【DP】洛谷 P1854 花店橱窗布置

你们好啊,我又回来了。P1854这是一道动态规划题。 还是先放题目,防止走错》》》 这次先放数据,上次就忘了.......... 其实我们看见数据只有100,其实就可以考虑瞎搞,什么记忆化搜索,题解还有人求最长路,闲的无聊你可以自己尝试。 我这里介绍一下这题正解 -> dp。 其实很显然的吧........ 这个题,我们考虑我们考虑用f[i][j...

AcWing 2879. 画中漂流(简单DP)

在梦境中,你踏上了一只木筏,在江上漂流。 根据对当地的了解,你知道在你下游 D 米处有一个峡谷,如果你向下游前进大于等于 D 米则必死无疑。 现在你打响了急救电话,T 秒后救援队会到达并将你救上岸。 水流速度是 1 米/秒,你现在有 M 点体力。 每消耗一点体力,你可以划一秒桨使船向上游前进 1 米,否则会向下游前进 1 米(水流)。 M 点体力需在救援队...

算法学习笔记——感知机原理及其代码实现

感知机原理及代码实现 上篇讲完梯度下降,这篇博客我们就来好好整理一下一个非常重要的二分类算法——感知机,这是一种二分类模型,当输入一系列的数据后,输出的是一个二分类变量,如0或1 1. 算法原理 1.1 知识引入 说起分类算法,博主想到的另一个算法是逻辑回归,而感知机从原理上来说和回归类算法最大的区别就是引入了几何的思想,将向量放到了高维空间上去想象。首先...