链接:https://share.weiyun.com/5LzbzAc

目录

前言

斜率优化前期准备

1.从状态转移方程出发

2.推理状态转移方程

对结论的进一步推导

干货!综合结论

判断斜率大小的方法:叉乘

正片开始:代码部分

后记




前言

之前我们对DP优化已经有了很多的了解,比如:单调队列优化DP,四边形优化DP。

今天,我们要讲的是传说中的斜率优化。它与四边形优化相似,都是从数学角度上来推理。斜率优化在四边形优化上又需要更复杂的数学推理,其中涉及到的求一条直线的斜率,以及运用叉乘来判断两条直线的斜率大小,都足以让初学者望而生畏。但只要理解透了其中的数学方法,也就是那么回事,挺简单的(我们老师说能第一次理解的人很少,所以如果你看不懂得话,可以第二天再来看一遍)。我在理解斜率优化时也是非常的煎熬,现在也是刚刚入门,所以有些地方写的有纰漏的话请见谅。


斜率优化前期准备

1.从状态转移方程出发

既然是一种优化DP的方法,那么他的起点就一定离不开状态转移方程。所以让我们来看一下这个转移方程

这是一个很简单的状态转移方程,很容易想到用单调队列优化把时间复杂度降到。(如果你不知道单调队列优化DP,应该看看,因为接下来的斜率优化也要用到它)

How about this?(那么这个转移方程呢?)

观察这个转移方程,现在它还能用单调队列优化吗?显然不能。拆开这一项,会得到,它不能分解为只与i或j有关的部分。于是上面的单调队列优化方法就不好使了,于是就引出了我们今天要学的内容:斜率优化了。

先来看看此题:Print Article

•题目大意:输出N个数字a[N],输出的时候可以连续的输出,每连续输出一串,它的费用是 “这串数字和的平方加上一个常数M”。n<=500000,求最下的费用。

此题转移方程就是(sum为前缀和也就是a1+a2+...+ai

假设i是当前循环最外层,也就是可以假设它是一个常量。在不优化的情况下,我们是把j从1到i-1循环,找出最小决策点j来使最小。所以我们的主要任务就是找出最优决策点j。下面让我们来看看是怎么推出斜率优化的。

2.推理状态转移方程

我们考虑两个决策点k与j,如果决策j更优,那么也就是,好了,现在只要用一下你们的纸和笔,就可以推出这个式子:

似乎只能化简这么多了。但让我们观察不等式的右边这一项,因为我们已经规定了sum为前缀和数组;

所以如果j>k,则sum[j]-sum[k]是>0的,化简后得;

如果j<k,则sum[j]-sum[k]<0,得

观察这个式子,我们可以把设为,设为(相当于把x,y,看成两个函数),则上式可以化简为:

如果有j>k,则sum[j]-sum[k]<0,有等价于决策j优于k

如果有j<k,则sum[j]-sum[k]<0,有等价于决策j优于k

(这里讲的有点快,说一下,sum[i]已经被设为常数了)

既然我们已经把他们设为x,y了,则可以把这两个式子代入平面直角坐标系里(滑稽),那么j和k就是坐标系上的两个点,所以就是两个坐标的直线的斜率(如果你不懂什么是斜率,请看这里)。

那么我们就可以得出我们的第一个结论:如果两个决策点的斜率小于sum[i],则靠后的决策点更优;否则靠前的决策点更优。——结论1.斜率则是程序中是用函数来实现的。

也就是说在我们碰到其他题时,也需要用类似的方法对状态转移方程进行化简,并求出函数x和函数y.


对结论的进一步推导

尽管我们已经得出了一个逼格很高的结论,但这对于斜率优化是远远不够的,所以我们还要对此结论进一步推导。

先设一个函数g(i,j)为点i和点j之间的斜率,也就是。然后,让我们考虑3个决策点的情况:i,j,k,k<j<i且(也就是k到j的斜率大于i到j的斜率。为了方便理解,这里给出图像)。

下面让我们利用我们刚才得出的结论1, 升级我们的结论。

可以分三种情况来讨论,设结论1里的sum[i]为一个常量P。

(当我们在考虑斜率大小时,我们可以这样想:斜率越大,直线就越斜。上图中的就比要斜)

这里贴出结论方便结合图理解:如果两个决策点的斜率小于P,则靠后的决策点更优;否则靠前的决策点更优。

•1.如果均小于P,则j比i优,k比j优。所以最优决策点为k

•2.如果均大于P,则j比k优,i比j优。所以最优决策点为i

•3.如果g[i][j]<P且g[j][k]>P,则i比j优,k比j优。所以最优决策点不为j

根据对3个情况的推理,我们发现不论如何,j都无法成为最佳决策点,所以可以排除j。于是就得出了我们的第2个论断:所有的决策点满足一个下凸包性质,也就是最优决策点的斜率是单调递增的。

下凸包就是如图的情况:

这里我们就可以代入单调队列了。为什么?因为由上述我们得到的结论,我们知道这些最优决策点相邻之间的斜率是单调递增的。将这些决策点放入队列,这个队列就具有了单调性,也就是可以用单调队列来实现了。

设这个单调队列q里存的决策点a,b,a<b,当前的数组sum[i]是会随着i递增的;当我们的最优决策点取在b时,b前面的所有决策点都将无效!为什么?因为sum[i]是递增的,所以需要我们匹配的斜率也是越来越大的,因为a<b,a前面的所有决策点都小于a,所以a及a前面的所有决策点都将无效。(在程序中,我们在单调队列之中的实现就是弹出队头。)但随着i的增加,b也可能不再适用,于是可以拿b与b后一个点c的斜率与sum[i]作比较:如果<sum[i],则弹出b。知道为止。


干货!综合结论

所以我们对程序的优化应该这样来实现:

•1,用一个单调队列来维护所有决策点。

•2,找最佳决策点时,设当前求解状态为i,从队头开始,如果已有元素a b c,当i点要求解时,如果<sum[i],那么说明b点比a点更优,a点可以排除,于是a出队,直到第一次遇到>sum[i],此时j-1即为最佳决策点。

•3,假设队列中从头到尾已经有元素a b c。那么当d要入队的时候,我们维护队列的下凸性质,即如果<,那么就将c点删除。直到找到>=为止,并将d点加入在该位置中。

Ps:写到这里有点写不下去了,于是直接粘贴了老师课件的原话。所以结合一下待会儿的代码凑合着看吧。。。。。。


判断斜率大小的方法:叉乘

(这个部分原来是没有的,现在抽出空来补充一下)

因为上文涉及到判断斜率的大小,但通常我们判断斜率是直接通过比较大小来比较的。虽然这种方法很方便,但因为涉及到除法的精度问题,这里引入一个叉乘的概念。

要运用叉乘来判断斜率的大小,我们还要知道一个东西叫向量,通常用字母p来表示。

向量可以看成一条线段。在平面直角坐标系中,如果有一条直线,它的一个端点坐标为(x1,y1),另一个端点坐标为(x2,y2)。

那么这条线段的线段就为p(x1-x2,y1-y2)。(相当于把这条线段平移到了原点)

(记住,结论中我们也将那些决策点连成了线段)

设有二向量p1(x3,y3),p2(x4,y4)。

则它们的叉乘p1×p2=(x3*y4-x4*y3)。在物理上,它们可以表示为一个平行四边形的有向面积。所以:

•若p1×p2>0,则p2在p1的逆时针方向;

•若p1×p2<0,则p2在p1的顺时针方向;

•若p1×p2=0,则p1与p2方向重合。

所以我们可以就可以结合上面的结论的最后一条。因为我们要保证最后队尾三个决策点为下凸包,所以我们在从队尾入队时,先将向量求出来,再用叉乘判断它们是否为下凸包。如果是,则退出循环;如果不是,则弹出队尾。


正片开始:代码部分

注释已经打好了,结合着上面的结论看吧.

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>using namespace std;#define infI 0x3f
#define infL 1LL<<60
#define N 500010
#define LL long long
#define mem(a,n) memset(a,n,sizeof(a));LL read() {LL f=1,s=0;char a=getchar();while(!(a>='0'&&a<='9')) { if(a=='-') f=-1 ; a=getchar(); }while(a>='0'&&a<='9') { s=s*10+a-'0'; a=getchar();}return f*s;
}LL sum[N],f[N],q[N];
int head,tail=1,n,m;LL px(LL i) {//求一个点的横坐标xreturn sum[i]*2;
}LL py(LL i) {//求一个点的纵坐标yreturn sum[i]*sum[i]+f[i];
}int main() {while(cin>>n>>m) {//循环读入mem(f,0);mem(q,0);head=0,tail=1;for(int i=1;i<=n;i++)//初始化sum数组sum[i]=read();for(int i=1;i<=n;i++)sum[i]+=sum[i-1];for(int i=1;i<=n;i++) {while( head+1<tail && (py(q[head+1])-py(q[head])) <= sum[i]*( px(q[head+1]) - px(q[head] ) ) )//实现总结论的第2条;(py(q[head+1])-py(q[head]))求的是斜率。//PS:你们可能看不懂,这里为了避免循环的精度问题进行了移项。head++;f[i]=f[q[head]]+m+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]]);//动态规划while( head+1 < tail && (px(q[tail-1])-px(q[tail-2]))*(py(i)-py(q[tail-1])) <= (px(i)-px(q[tail-1]))*(py(q[tail-1])-py(q[tail-2])))//判断斜率大小,这里运用了叉乘的方法。  tail--;q[tail++]=i;}cout<<f[n]<<endl;}}

后记

在写博客的过程中,我自己也感觉到了对斜率优化不是很了解,相当于复习了一遍,还是很感慨的.

C++剑指offer:解题报告之DP优化学习记 (二) ——浅论DP斜率优化 (Print Article 【HDU - 3507】 )相关推荐

  1. 剑指offer解题思路锦集11-20题

    又来更新剑指offer上的题目思路啦. 11.[二进制中1的个数] 题目:输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. eg:NumberOf1(1)=1 NumberOf1(2) ...

  2. 剑指offer解题记录(JAVA)

    面试题3:数组中重复的数字 题目链接 import java.util.Arrays;/*** P39 面试题3:数组中重复的数字* 在一个长度为n的数组里所有数字都在0~n-1的范围内 数组中某些数 ...

  3. java翻转单词顺序split_剑指offer解题报告(Java版)——翻转单词顺序 左旋字符串 42...

    引言 这种翻转的问题会遇到很多,其实就是一个倒序的问题,对于第一个题只是想翻转单词的顺序,而并不想把整个字符串翻转了,如果完全翻转的话,比如I am a student.中所有字符翻转得到.tnedu ...

  4. java计算筛子概率_剑指Offer解题报告(Java版)——n个骰子的点数 43

    问题 n个骰子朝上的数之和为s,求s的所有可能以及概率 分析问题 如果是用笨方法,一般人最开始都会想到笨方法,那就是枚举法 举个例子,比如两个骰子,第一个骰子的结果为1,2,3,4,5,6,两个骰子的 ...

  5. java 数组中某个数出现的概率_剑指Offer解题报告(Java版)——排序数组中某个数的个数 38...

    分析问题 问题只需要找到排序数组中某个数K的个数,由于已经是排序了,K一定是在一堆的,所以我们只需要找到第一个K的index1,然后找到最后一个K的index2就可以了 而寻找K的过程我们一般通过二分 ...

  6. 过分!虾皮被曝大范围毁约;深度学习技巧全辑;MongoDB开源替代 4.7K★;剑指Offer解题代码;大数据算法笔记汇总;前沿论文 | ShowMeAI资讯日报

    ShowMeAI日报系列全新升级!覆盖AI人工智能 工具&框架 | 项目&代码 | 博文&分享 | 数据&资源 | 研究&论文 等方向.点击查看 历史文章列表, ...

  7. asp 判断数组等于_剑指Offer(牛客版)--面试题4:二维数组中的查找

    SCDN博客:https://blog.csdn.net/weixin_41923658 微信公众号:「汤姆鱼」 -------------------------------------手动分割线- ...

  8. 剑指offer编程题Java实现——面试题3二维数组中的查找

    题目描述 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 下面是我实现的代码,修 ...

  9. 【大总结3】leetcode解题总览(算法、剑指offer、SQL、多线程、shell)

    3/22更新 剑指offer 题目链接 建议大部分题都会做,都能比较快速且准确的写出来.关于做题方式,我的建议是:一道一道刷即可,因为难度一般,不用系统的学习什么知识,遇到实在不会的就跳过即可. 我这 ...

最新文章

  1. Zabbix安装(十):监控windows进程
  2. 表数据比图数据更难处理
  3. POJ3263-Tallest Cow【前缀和】
  4. js中变量作用域的小理解
  5. Hadoop概念学习系列之谈谈RPC(三十三)
  6. 使用sqoop导出mysql数据时错误处理【com.mysql.jdbc.RowDataDynami】
  7. 开源 免费 java CMS - FreeCMS1.3-信息管理
  8. 洛谷P1074 靶形数独 [搜索]
  9. echarter: ECharts的R语言接口(一)
  10. Linux下安装并启动MongoDB
  11. matlab边的介数,matlab-bgl-master 复杂网络工具包,便于计算 边介数,最短路径等问题 261万源代码下载- www.pudn.com...
  12. 如何对金蝶kis进行结转损益操作
  13. java 车牌正则表达式_车牌正则表达式
  14. uniapp app端调起高德地图导航
  15. 企业微信 之 网页鉴权并与公司后台关联
  16. 父母的房产继承买卖赠予以及网络红包代金券优惠券的国家最新税法规定
  17. 医保业务综合服务终端技术规范_增值税发票综合服务平台出口退税业务操作指引...
  18. sgm3157功能_SGM3157
  19. 每天自我提升的8个好习惯
  20. C++ 栈的括号匹配

热门文章

  1. 悼念贝娜齐尔#183;布托
  2. 越狱Season 1-Episode 12:Odd Man Out
  3. c语言程序设计新编教程答案钱雪忠,新编C语言程序设计教程
  4. 甲方乙方——如何协调设计师与客户之间的矛盾
  5. android gps 电子围栏,基于GPS的电子围栏设计
  6. HTML5前期学习准备(一)
  7. 165体重_女性身高155cm—165cm,体重多少合适?有个实情告诉你,别瞎减肥
  8. 排查和判断常见的服务器故障
  9. 二级java pdf_全国计算机等级考试二级Java语言程序设计.PDF
  10. Flink Forward Asia 2019 总结和展望 - 附PPT下载