文章目录

  • 斜率优化引入
  • 从例题开始
    • 斜率优化Part 1: 推为斜率式
    • 斜率优化Part 2: 合法点集的斜率单调性
    • Part 3: 找到最优决策点
    • Part 4: 斜率优化大流程
    • Part 5: 斜率优化的优化
    • Part 6: 代码详解
  • 斜率优化的一些问题
  • 例题详解
    • [NOI2014 D2T1]购票
      • Summary
    • 【CEOI2017】Building Bridges

斜率优化引入

在说斜率优化之前,我们先说说一种更为简单的dpdpdp优化。

比如,有一个dpdpdp式子形如dpi=min⁡0≤j<i{dpj+f(j)+g(i)}dp_i=\min_{0≤j<i} \{dp_{j}+f(j)+g(i)\}dpi​=0≤j<imin​{dpj​+f(j)+g(i)}

可以发现,这个式子中的每一项要么只与iii有关,要么只与jjj有关。

这个式子可以转化为dpi=min⁡0≤j<i{dpj+f(j)}+gidp_i=\min_{0≤j<i} \{dp_{j}+f(j)\}+g_idpi​=0≤j<imin​{dpj​+f(j)}+gi​

于是,我们可以在枚举iii的同时,维护一个dpj+f(j)dp_j+f(j)dpj​+f(j)的最小值minvminvminv。更为具体地说,每次我们先将dpidp_idpi​的值赋为minv+giminv+g_iminv+gi​,再将dpi+f(i)dp_i+f(i)dpi​+f(i)来更新minvminvminv。


但是,如果这个式子中含有同时与 i,ji,ji,j 有关的项,就显然不能用这种dpdpdp优化了。

比如,有一个dpdpdp式子形如dpi=min⁡0≤j<i{dpj+aj+bicj}dp_i=\min_{0 \le j<i} \{dp_{j}+a_j+b_i c_j\}dpi​=0≤j<imin​{dpj​+aj​+bi​cj​}

此时我们很难找到 dpidp_idpi​ 的最优决策点 jjj。

这个 dp 式子的转移优化,有三种最常见方式:
①决策单调性优化 dp;
②斜率优化 dp;
③高级数据结构优化 dp(比如李超线段树)。

本篇文章,主要谈一谈②的具体优化方法。

从例题开始

【APIO2010】特别行动队

斜率优化Part 1: 推为斜率式

不难写出状态转移式:

dpi=max⁡0≤j<i{dpj+a(prei−prej)2+b(prei−prej)+c}dp_i=\max_{0≤j<i} \{dp_j+a (pre_i-pre_j)^2+b(pre_i-pre_j)+c\}dpi​=0≤j<imax​{dpj​+a(prei​−prej​)2+b(prei​−prej​)+c}

我们对max⁡\maxmax后面的部分进行推导:

dpj+a(prei−prej)2+b(prei−prej)+cdp_j+a (pre_i-pre_j)^2+b(pre_i-pre_j)+cdpj​+a(prei​−prej​)2+b(prei​−prej​)+c
=dpj+aprei2−2apreiprej+aprej2+bprei−bprej+c=dp_j+a pre_i^2-2\ a\ pre_i\ pre_j+a\ pre_j^2+b\ pre_i-b\ pre_j+c=dpj​+aprei2​−2 a prei​ prej​+a prej2​+b prei​−b prej​+c

其中有一些项与jjj无关,我们可以把这些项看做常量并将它们取走。

剩下的式子为 dpj−2apreiprej+aprej2−bprejdp_j-2\ a\ pre_i\ pre_j+a\ pre_j^2-b\ pre_jdpj​−2 a prei​ prej​+a prej2​−b prej​

对于式子中仅仅与jjj有关的项,无论 iii 如何变化这个值都是不变的。所以可以将这些项的和压缩为一个函数 fff。令 f(j)=dpj+aprej2−bprejf(j)=dp_j+a\ pre_j^2-b\ pre_jf(j)=dpj​+a prej2​−b prej​。

式子变成了f(j)+(−2aprei)prejf(j)+(-2\ a\ pre_i)pre_jf(j)+(−2 a prei​)prej​。

再令k(i)=−2apreik(i)=-2\ a\ pre_ik(i)=−2 a prei​,于是最终的式子是f(j)+k(i)prejf(j)+k(i)\ pre_jf(j)+k(i) prej​。决策点jjj是满足 f(j)+kprejf(j)+k\ pre_jf(j)+k prej​ 最大的jjj。

考虑: 如果 j1<j2j1<j2j1<j2 并且 j1j1j1 不比 j2j2j2 优,当且仅当

f(j1)+kprej1≤f(j2)+kprej2f(j1)+k\ pre_{j1}≤f(j2)+k\ pre_{j2}f(j1)+k prej1​≤f(j2)+k prej2​

移项得

k(prej1−prej2)≤f(j2)−f(j1)k\ (pre_{j1}-pre_{j2})≤f(j2)-f(j1)k (prej1​−prej2​)≤f(j2)−f(j1)

由于序列中的每个数都是非负数,所以 prej2>prej1pre_{j2}>pre_{j1}prej2​>prej1​,即 prej1−prej2<0pre_{j1}-pre_{j2}<0prej1​−prej2​<0。

根据⌊\lfloor⌊ 不等式两边同时乘或除一个负数,不等式方向改变 ⌉\rceil⌉,这个式子可以变成

k≤f(j2)−f(j1)prej1−prej2k≤\frac {f(j2)-f(j1)} {pre_{j1}-pre_{j2}}k≤prej1​−prej2​f(j2)−f(j1)​

根据k=−2apreik=-2\ a\ pre_ik=−2 a prei​,代入得

−2aprei≤f(j2)−f(j1)prej1−prej2-2\ a\ pre_i≤\frac {f(j2)-f(j1)} {pre_{j1}-pre_{j2}}−2 a prei​≤prej1​−prej2​f(j2)−f(j1)​

等式两边再同时乘−1-1−1:

2aprei≥f(j2)−f(j1)prej2−prej12\ a\ pre_i≥\frac {f(j2)-f(j1)} {pre_{j2}-pre_{j1}}2 a prei​≥prej2​−prej1​f(j2)−f(j1)​

f(j2)−f(j1)prej2−prej1≤2aprei\frac {f(j2)-f(j1)} {pre_{j2}-pre_{j1}}≤2\ a\ pre_iprej2​−prej1​f(j2)−f(j1)​≤2 a prei​

至此推式子的步骤全部结束。

为什么要推到这一步呢?可以发现,推到这一步之后,不等式右边的那一部分是一个斜率的形式。更具体地,假设在平面直角坐标系上有两个点A,BA,BA,B,AAA的横坐标为prej1pre_{j1}prej1​且纵坐标为f(j1)f(j1)f(j1),BBB的横坐标为prej2pre_{j2}prej2​且纵坐标为f(j2)f(j2)f(j2),那么f(j2)−f(j1)prej2−prej1\frac {f(j2)-f(j1)} {pre_{j2}-pre_{j1}}prej2​−prej1​f(j2)−f(j1)​就等价于同时过AAA与BBB的直线的斜率

此时可以采用斜率优化

斜率优化Part 2: 合法点集的斜率单调性

提炼一下Part1Part\ 1Part 1中我们得到的结论:如果两个点相连的直线的斜率不大于k=2apreik=2\ a\ pre_ik=2 a prei​,那么前面那个点对应的 jjj 就一定不是最优决策点;反之,后面那个点对应的jjj就一定不是最优决策点。

这有什么用呢?

先考虑三个点的简化情况,假设从 111 号点到 222 号点的斜率为 xxx,从 222 号点到 333 号点的斜率为 yyy。令 x>yx>yx>y。


图中的每一个点都对应着一个转移点jjj,我们给这三个点从左到右依次标号为 1,2,31,2,31,2,3 号点。

可以发现,此时无论kkk的取值如何,222号点都不会成为最优转移点。为什么呢?我们通过分类讨论来证明:

(1)2−32-32−3点的连线的斜率必须大于kkk,222号点才可能成为最优决策点。形式化地表示为y>ky>ky>k。
(2)1−21-21−2点的连线的斜率必须不大于kkk,222号点才可能成为最优决策点。形式化地表示为x≤kx≤kx≤k。

由于x>yx>yx>y,所以绝对不会存在任何一个实数kkk满足y>ky>ky>k且x≤kx≤kx≤k。

综上所述,222号点无论如何都不能成为最优决策点

根据上面的推导,222号点已经“废了”,我们直接将它删去!


现在我们处理完了333个点的情况,考虑扩展到很多点的情况。

可以发现,如果存在三个横坐标递增的点,满足前两个点的斜率大于等于后两个点的斜率,那么就可以删去中间的那个点

所以,如果我们处理出一个不可删点的点集的斜率数组(每相邻两个数的斜率),那么这个数组必然是递增的。

Part 3: 找到最优决策点

不难发现,在一个不可删点的点集中,最优决策点jjj满足:

①点j−1j-1j−1与jjj相连的直线的斜率不大于kkk;
②点jjj与j+1j+1j+1相连的直线的斜率超过kkk。

由于这些点的斜率有单调性,所以可以通过二分来找到最优决策点jjj。

Part 4: 斜率优化大流程

枚举iii,维护不可删点点集sss。假设sss的长度为lenlenlen,两个点i,ji,ji,j相连直线的斜率为slope(si,sj)slope(s_{i},s_j)slope(si​,sj​)。

①二分找到最优决策点jjj;
②执行状态转移;
③不停地弹出队尾,直到slope(slen−1,slen)≤slope(slen,pi)slope(s_{len-1},s_{len})≤slope(s_{len},pi)slope(slen−1​,slen​)≤slope(slen​,pi)或s≤1s≤1s≤1。这里pipipi表示iii对应的点;
④将pipipi压入sss中。

时间复杂度为O(nlog⁡n)O(n \log n)O(nlogn)。

斜率优化的精髓在于不可删点点集的单调性。正是因为这个单调性,我们才能采用二分去快速寻找最优决策点,从而将时间复杂度优化为O(nlog⁡n)O(n \log n)O(nlogn)。

Part 5: 斜率优化的优化

对于一些特定的题目,我们可以去掉的 log⁡\loglog。

比如对于本题(特别行动队)而言,k(i)k(i)k(i)是有单调性的;所以,每次我们可以不停地删去队头,直到队头满足最优决策点的性质;删完之后队头就是最优决策点。

此时,每个元素至多入队一次出队一次,时间复杂度 O(n)O(n)O(n)。

Part 6: 代码详解

斜率优化的代码历来非常短,甚至连1KB都没有,但是细节非常多。斜率优化的注意事项非常重要,这里有必要阐述一下:

①斜率优化有时可能会爆精度,比较两个斜率的时候可以用交叉相乘,比如这一题就卡了精度,必须采用这种方法才能A掉。

②请注意如果sss(不可删点点集)的大小已经是111了,就不要再删点了。 111是边界情况,000不是。

③在开始做dpdpdp之前,sss的大小应赋为111且其中的唯一一个数应为000。不然不会考虑从000转移的情况

④少用doubledoubledouble,输出坚决不用doubledoubledouble。不然会自动转化为科学计数法的形式,从而“玄学”WA。我曾因此调了1.5h1.5h1.5h,最后把几个doubledoubledouble换成intintint才终于A掉。

⑤计算斜率必须用longdoublelong\ doublelong double,除非您采用了交叉相乘;

⑥请注意,只有 kkk 有单调性的时候才能用本种写法;否则,请乖乖二分,O(nlog⁡n)O(n \log n)O(nlogn)。

另外,普通的 O(n2)O(n^2)O(n2)的 dpdpdp 往往不难写,可以将自己的斜率优化dpdpdp的代码与朴素dpdpdp对拍,用于查错与检验。

//[APIO2010]特别行动队 AC Code
#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
const int maxl=1000005;int read(){int s=0,w=1;char ch=getchar();while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}return s*w;
}
int n,A,B,C,head=1,tail=1,q[maxl],a[maxl];
int pre[maxl],dp[maxl];double Y(int x){return A*pre[x]*pre[x]-B*pre[x]+dp[x];}
double X(int x){return pre[x];}
double calc(int x,int y){return (Y(y)-Y(x))/(X(y)-X(x));}signed main(){cin>>n>>A>>B>>C;for (int i=1;i<=n;i++)  a[i]=read(),pre[i]=pre[i-1]+a[i];for (int i=1;i<=n;i++){while (head<tail&&2*A*pre[i]<=calc(q[head],q[head+1]))  head++;int j=q[head];dp[i]=dp[j]+A*(pre[i]-pre[j])*(pre[i]-pre[j])+B*(pre[i]-pre[j])+C;while (head<tail&&calc(q[tail-1],q[tail])<=calc(q[tail],i))  tail--;q[++tail]=i;}cout<<dp[n]<<endl;return 0;
}

现在,想必你已经对斜率优化有了一个基本的了解。

本题为斜率优化的简单模板,同时还有Luogu P5785, Luogu P3195供您练习。前者涉及到一个 dpdpdp 的套路,后者与特别行动队类似。

斜率优化的一些问题

Q: 如果kkk没有单调性怎么办?
A: 不能去队首了,每次必须通过二分来找最优决策点。时间复杂度是O(nlog⁡n)O(n \log n)O(nlogn)的。

Q: 当iii增大的时候,如果横坐标没有单调性怎么办?
A: 此时,我们每次将iii对应的横坐标给插入对应的位置,然后查询kkk的前驱的位置编号并将其作为最优决策点来转移。此时我们要维护查询前驱后继以及插入的操作,可以采用平衡树来维护,比如这题。当然,也可以使用李超线段树来维护。

Q: 我们该如何推式子呢?你推了好久才得到最终的斜率式,中途有没有一些套路呢?
A: 有套路的。推式子的大致步骤如下:
①把括号全部拆掉;
②把仅仅与iii有关的项取出;
③把仅仅与jjj有关的项压缩为一个函数并带入;
④剩下的是与i,ji,ji,j均有关的项——对于这些项,先合并同类项。如果剩下的项的数量不超过111,那么我们就将这个式子给简化为a(i)b(j)a(i)\ b(j)a(i) b(j)的形式并带入;
⑤定义j1,j2j1,j2j1,j2,考虑如果j1<j2j1<j2j1<j2且j1j1j1比j2j2j2差会怎样,需要采用不等式的推导技巧;
⑥假定横坐标递增(就算不递增也用平衡树让它递增了),得到斜率式。

Q: 如果与i,ji,ji,j均有关的项在合并同类项之后剩下的项的数量超过了111咋办?
A: 对不起,凉拌。斜率优化无法处理这种东西,请考虑决策单调性优化dpdpdp,比如这一题。

Q: 能不能给我一些斜率优化的好题?
A: 到处都是,这里给一个不错的题单。


例题详解

这些例题,各有各的特点以及套路。

[NOI2014 D2T1]购票

我们从部分分入手,一个一个考虑。

30pts: 暴力dp

状态设计: dpidp_idpi​ 表示从节点iii出发的答案;
状态转移: dpi=min⁡j∈ancestor(i),dis(i,j)≤li(dpj+pidis(i,j)+qi)dp_i=\min_{j∈ancestor(i),dis(i,j)≤l_i} (dp_j+p_i\ dis(i,j)+q_i)dpi​=j∈ancestor(i),dis(i,j)≤li​min​(dpj​+pi​ dis(i,j)+qi​)

于是采用暴力 dp 即可。

50pts: 斜率优化

可以发现,30pts解法中的式子可以采用斜率优化。为了方便叙述,下面的preipre_iprei​表示从iii到根节点的路径长度。

dpj+pidis(i,j)+qidp_j+p_i\ dis(i,j)+q_idpj​+pi​ dis(i,j)+qi​
=dpj+pi(prei−prej)+qi=dp_j+p_i(pre_i-pre_j)+q_i=dpj​+pi​(prei​−prej​)+qi​
=dpj+piprei−piprej+qi=dp_j+p_i\ pre_i-p_i\ pre_j+q_i=dpj​+pi​ prei​−pi​ prej​+qi​
=(dpj)−piprej+(piprei+qi)=(dp_j)-p_i\ pre_j+(p_i\ pre_i+q_i)=(dpj​)−pi​ prej​+(pi​ prei​+qi​)

piprei+qip_i\ pre_i+q_ipi​ prei​+qi​与jjj无关,暂时移除。剩下的部分是dpj+(−pi)prejdp_j+(-p_i)pre_jdpj​+(−pi​)prej​,而这是一个斜率优化的套路式

令j1<j2j1<j2j1<j2为两个候选决策点,其中j1j1j1比j2j2j2差。形式化地:

dpj1+(−pi)prej1≥dpj2+(−pi)prej2dp_{j1}+(-p_i)\ pre_{j1}≥dp_{j2}+(-p_i)\ pre_{j2}dpj1​+(−pi​) prej1​≥dpj2​+(−pi​) prej2​
(−pi)(prej1−prej2)≥dpj2−dpj1(-p_i)(pre_{j1}-pre_{j2})≥dp_{j2}-dp_{j1}(−pi​)(prej1​−prej2​)≥dpj2​−dpj1​
pi≥dpj2−dpj1prej2−prej1p_i≥\frac {dp_{j2}-dp_{j1}} {pre_{j2}-pre_{j1}}pi​≥prej2​−prej1​dpj2​−dpj1​​

由于pip_ipi​没有单调性,所以必须二分找最优决策点。每次向孩子节点搜索完毕后,将所做的操作依次撤销并返回即可。

时间复杂度O(nlog⁡n)O(n \log n)O(nlogn)。


考虑正解。

正解的状态转移有一个致命的问题: 每个节点能成为合法决策点,当且仅当它与iii的距离不超过lil_ili​。显然合法决策点一定是以iii的父节点为一端的一条连续的链,我们可以通过预处理求出每一个节点iii的合法决策点的深度最小能是多少。

我们向下搜索,维护一个数组记录下所有当前经过的节点的,返回时去除该数组中的末位,于是维护的就是iii的所有祖先节点。对于一个iii,这个数组中的合法转移点均在长度为limilim_ilimi​的后缀中。

由于横坐标递增,我们能不能直接在这个后缀中二分查找呢?

显然不行。因为可能会出现下面这种情况:


图是盗的

比如查询其中长度为333的后缀,在这个后缀中B是合法的转移点,但是在整体中BBB并不是合法的转移点,因此已经去掉了它,所以漏掉了一个合法的点

考虑对于后缀查询能否用线段树来维护。线段树上的每个节点维护一个单调栈,表示在此区间中的不可删点的点集。每次修改,我们找到所有要修改的区间,逐一执行单调栈修改;对于一次查询,我们将后缀拆成一些区间,对于每一个线段树区间二分查询此区间中的最优决策点

对于跨区间的节点形成的斜率,分别尝试转移更新答案。由于这一类节点至多只有log⁡n\log nlogn个,所以直接转移时间复杂度仍然正确。

修改均摊O(log⁡n)O(\log n)O(logn)。
查询单次O(log⁡2n)O(\log^2 n)O(log2n)。

综上所述,我们采用线段树套单调栈将时间复杂度优化到了O(nlog⁡2n)O(n \log^2 n)O(nlog2n)。

Summary

对于这一类转移点必须在一个区间中的dpdpdp题,可以在最外层套上一棵线段树。

顺便说一句,本题采用树状数组套单调栈是一种更优常数的做法,留给读者思考。

【CEOI2017】Building Bridges

由于点的横坐标与查询的kkk均没有单调性,采用平衡树维护凸壳即可。

或者直接用李超线段树维护,也是可以的。

斜率优化详解(超详细, 有图有代码有注释)相关推荐

  1. Java 泛型详解(超详细的java泛型方法解析)

    Java 泛型详解(超详细的java泛型方法解析) 1. 什么是泛型 泛型:是一种把明确类型的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型.也就是说在泛型使用过程中,操作的数据类型被指定为 ...

  2. 【bind()函数】JavaScript手写bind()及详解-超详细~~~

    这两天学习了手写call.apply.bind,手写bind思考了很久才实现了MDN的示例的结果,所以记录下来~ 因为是第一篇文章,所以可能存在一些错误,希望各位大佬批评指正,不吝赐教. 也欢迎不懂的 ...

  3. @Autowired注解详解——超详细易懂

    @Autowired详解 要搞明白@Autowired注解就是要了解它是什么?有什么作用?怎么用?为什么? 首先了解一下IOC操作Bean管理,bean管理是指(1)spring创建对象 (2)spr ...

  4. mysql 联表比对,MySQL联表查询详解/超详细mysql left join,right join,inner join用法分析比较...

    超详细mysql left join,right join,inner join用法分析 下面是例子分析 表A记录如下: aID aNum 1 a20050111 2 a20050112 3 a200 ...

  5. log4j 配置详解(超详细)

    一.Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合使 ...

  6. Microsoft SQL Server 2019 下载、安装及Java JDBC配置连接数据库(多图详解 超详细)

    一.下载 下载链接Microsoft SQL Server 二.安装 1.找到刚刚下载的文件,双击打开后,选择基本并接受 2.选择接受 3.选择安装位置,并点击安装,然后等待下载安装完成 4.正在安装 ...

  7. 史上最全测试流程详解----超详细

    前言----- 对于测试流程基本很多做过测试的大牛,小哥哥,小姐姐都能说出个十之八九,但是对于细节,可能还需要一些整理文件,这不,我整理了一些测试的全部流程,希望能给大家带来帮助,有不妥的地方,请大家 ...

  8. web服务器常见配置搭建详解(超详细)

    前言: 本博客借鉴一些写的比较好的博客,进行归纳总结,整理了一篇比较详细的服务器常见配置搭建教程 一来是和大家一起分享,二来也是作为自己的学习笔记记录一下. 温馨提示: 篇幅较长,请分阶段选择性查看. ...

  9. 八大排序详解-超详细

    目录 概述 一,选择排序-直接插入排序(Direct insertion sort) 二,插入排序-希尔排序(Shell sort) 三,选择排序-简单选择排序(Simple selection so ...

最新文章

  1. IIS+PHP+MySQL+Zend Optimizer+GD库+phpMyAdmin安装配置[完整修正实用版]
  2. mysql 1z0_MySQL 8 OCP(1Z0-908)认证考试题库原题(第10题)
  3. OpenGL toon shading卡通着色的实例
  4. 一起学并发编程 - 优雅关闭
  5. 数据库优化 - MYSQL优化
  6. Docker下载与安装(win7,8,10,mac)
  7. 妙啊,小米11保护壳先小米11一步上市了...
  8. t–sql pl–sql_SQL串联正确完成–第1部分–可疑做法
  9. python构建知识库_Python学习知识库
  10. python面试题及答案 2019-这些2019年常考的Python面试题你都能答上来吗?
  11. 解决eeglab无法读取.mat文件(读取mat文件报错cannot read .mat file,eeglab error in function pop_editset()at line 445)
  12. elasticsearch单机版安装及安装过程踩的坑整理
  13. 二倍图三倍图什么意思_iOS 2倍图 3倍图适配小结
  14. 红帽8LINUX命令行使用技巧
  15. 计算机操作系统u盘的安装方法,怎么直接用u盘装系统操作教程
  16. pythonencoding etf-8_Python 量化分析ETF指数基金投资
  17. 126邮箱登录(selenium+python)
  18. 自动计数报警器c语言程序,计数报警器电路设计方案汇总(多款模拟电路设计原理图详解)...
  19. 163邮箱申请注册个人,163邮箱付费与免费版有什么区别?
  20. 怎么将CAD图纸转换成高清晰度PNG格式怎么操作?

热门文章

  1. 某查查app sign算法初步探索
  2. ahk编程_趣味AHK编程从入门到精通 - 主页
  3. 【Materials Studio学习二】建立三维晶体结构
  4. Collaborative Knowledge Base Embedding for Recommender Systems(译)
  5. 味尚食品 味尚拉面半干面是一种非常经典的中式面食
  6. 摩拜单车押金去向遭质疑 官方回应:不清楚。
  7. html 中的空格%3c br%3e,URL编码表一览 - frabbit的个人空间 - OSCHINA - 中文开源技术交流社区...
  8. linux6.8 增大 dev shm,增加/dev/shm大小
  9. OFFICE 2007 序列号换号方法
  10. 计算机网络详细配置,计算机网络:配置、设计与实战