可以用队列优化或斜率优化的dp这一类的问题为 1D/1D一类问题

即状态数是O(n),决策数也是O(n)

单调队列优化

我们来看这样一个问题:一个含有n项的数列(n<=2000000),求出每一项前面的第m个数到它这个区间内的最小值

可以使用RMQ求区间最小值,那么时间复杂度是O(nlogn),不是让人很满意。

dp[i]为i-m+1->i这个区间的最小值。

那么状态转移方程是

可以看出,这个题目的状态数是O(n),决策数是O(m),且决策的区间是连续的,那么可以尝试想办法把O(m)优化成O(1)

我们可以用单调队列维护一个数据结构,这个数据结构有两个域,pos和val,pos代表下标,val代表该下标所对应的值。队列中的pos单调递增,且val也单调递增

那么当计算一个状态时,只要从队首不断弹出pos<i-m+1的数据,只要pos>=i-m+1,那么该决策就是最优的,因为队列是单调的啊。

同理,同队尾插入一个数据时,只要不断剔除val比a[i]大的数据,直到遇到小于它的,然后将该数据插入队尾。

每个数据只入队列,出队列一次,所以时间复杂度是O(n),

分析:为什么插入的时候,比a[i]大的数据可以剔除,因为j<i时,a[j] > a[i], 那么以后所有的决策中,a[i]都比a[j]更优

  为什么可以不断删除pos<i-m+1的数据,因为i是递增的,该数据对当前的i没用,那么对以后的i也是没用的。

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
#pragma warning(disable:4996)
#pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long LL;
const int INF = <<;
/*
*/
const int N = + ;
int a[N];
int dp[N];
int q[N], head, tail;
int main()
{
int n, m;
while (scanf("%d%d", &n,&m) != EOF)
{
for (int i = ; i <= n; ++i)
scanf("%d", &a[i]);
head = tail = ;
q[tail++] = ;
dp[] = a[];
for (int i = ; i <= n; ++i)
{
while (head < tail && a[i] < a[q[tail - ]])//插入新的元素,要使得队列依旧单调递增
tail--;
q[tail++] = i;
while (head < tail && q[head] < i - m + )//剔除不合要求的pos
head++;
dp[i] = a[q[head]];
}
for (int i = ; i <= n; ++i)
printf("%d ", dp[i]);
puts("");
/*
5 3
1 2 3 4 5
1 1 1 2 3
*/
}
return ;
}

那么我们可以抽象出一类模型

需要注意到,上面要求可选的决策集是连续的。同时也可以注意到,当前决策所需要的值是不受现在的状态所影响的,即g(i)与w[x]是相互独立的。

斜率优化

我们在单调队列的最后说道,当前决策所需要的值是不受现在的状态所影响的,即g(i)与w[x]是相互独立的。

还有的1D/1D一类问题是想下面这样的。

但是如果状态转移方程是这样的: dp[i]=dp[j]+(x[i]-x[j])*(x[i]-x[j]) ,1<=j<=i   把括号化开后,得到2x[i]*x[j], 这使得当前决策所需要的值受当前状态的影响

所以上面单调队列的方法就不行了。

hdu3507

题目有n个字符要打印,连续打印k个字符的代价是(c1+c2+...+ck)^2 + m, m是题目所给的常量

题目要优化的是,如果连续打印过多,那么代价平方之后就会很大,如果连续打印过少,那么就多加几次m。

dp[i]表示打印第i个字符时的最小花费

dp[i] = min(dp[i],dp[j] + (sum[i]-sum[j])^2+m)  1<=j<i

那么复杂度是O(n^2),是无法接受的。

那么就需要优化了,

当j > k 且j比k优的时候

dp[j] + (sum[i]-sum[j])^2 + m < dp[k] + (sum[i]-sum[k])^2+m

化简得(dp[j]+sum[j]^2 - (dp[k]+sum[k])^2 )/ (2sum[j]-2sum[k]) < sum[i]

这就很像斜率表达式了,而且这个斜率表达式小于另一个斜率,即sum[i]。

从下图我们可以看出,当j为k优时,斜率为sum[i]的斜线过j点与y轴的截距更小。

令g[j,k]表示上面的式子

当k<j<i时,且g[i,j] <= g[j,k]时,j是可舍弃的。

如果所示,假设j成为最优,那么必须有sum[i] > g[j,k], 且 sum[i] < g[i,j], 即 g[j,k] < sum[i] < g[i,j],  也即有g[i,j] > g[j,k],但是与前提条件g[i,j] <= g[j,k]矛盾,所以假设不成立,所以j是可以舍弃的。

所以,我们要维护一个下凸的图形(即斜率不断增大),因为横坐标(也就是sum[j])是递增的,所以用一个队列维护就行了。如果横坐标不递增的话,就要用平衡树了。

如果,是一个下凸包,我们只要判断sum[i]所代表的斜线与哪个点在y轴上的截距最小就行了,又因为sum[i]是递增的,所以前面判断过的点不用再次判断。

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
#pragma warning(disable:4996)
#pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long LL;
const int INF = <<;
/*
普通的dp需要遍历以前的所有值,
斜率dp就是通过舍弃一些值,必须是当前可舍弃,以后也是可舍弃的值,从而减少遍历量
或者是使得队首的元素就是最优的, 关键是如何判断可舍弃,。。。。。通过数学分析斜率来舍弃??? 第i个字符肯定接在前面j个字符的后面,或者另起一行,但是费用该怎么算呢
dp[i][1]表示1另起一行 ,那么费用是 dp[i][1] = ci^2 + m + min(dp[i-1][0],dp[i-1][1])
dp[i][0] 表示接在前面j个字符的后面的最小费用 sum{cost[j]}^2+m + min(dp[j-1][0],dp[j-1][1]) */ const int N = + ;
int sum[N];
int dp[N];
int q[N], head, tail;
int getUp(int i, int j)
{
return dp[i] + sum[i] * sum[i] - (dp[j] + sum[j] * sum[j]);
}
int getDown(int i, int j)
{
return * sum[i] - * sum[j];
}
int main()
{
int n, m;
while (scanf("%d%d", &n, &m) != EOF)
{
for (int i = ; i <= n; ++i)
{
scanf("%d", &sum[i]);
sum[i] += sum[i - ];
}
head = tail = ;
q[tail++] = ;
for (int i = ; i <= n; ++i)
{
/* */
while (head + < tail && getUp(q[head + ], q[head]) <= sum[i] * getDown(q[head + ], q[head ]))
head++;
dp[i] = (sum[i] - sum[q[head]]) * (sum[i] - sum[q[head]]) + m + dp[q[head]];
while (head + < tail && getUp(i, q[tail - ])*getDown(q[tail - ], q[tail - ]) <= getUp(q[tail - ], q[tail - ])*getDown(i, q[tail - ]))
tail--;
q[tail++] = i;
}
printf("%d\n", dp[n]);
}
return ;
}

总结,

当横坐标递增,斜率递增时,用队列维护,可以在O(1)的时间内找到最优值,就是上面的情况。

当横坐标递增,斜率不递增时,我们可以二分,因为凸包的斜率是递增的,所以可以二分。

当横坐标不递增时,用平衡树维护。

【参考文献】

《1D1D动态规划优化初步》 作者:南京师范大学附属中学 汪一宁

《用单调性优化动态规划》   JSOI2009集训队论文

《斜率优化dp》

队列优化和斜率优化的dp的更多相关文章

  1. 斜率优化dp练习

    1.HDU3507 裸题,有助于理解斜率优化的精髓. dp[i]=min(dp[j]+m+(sum[i]-sum[j])2) 很显然不是单调队列. 根据斜率优化的的定义,就是先设两个决策j,k 什么时 ...

  2. 【学习笔记】动态规划—斜率优化DP(超详细)

    [学习笔记]动态规划-斜率优化DP(超详细) [前言] 第一次写这么长的文章. 写完后感觉对斜优的理解又加深了一些. 斜优通常与决策单调性同时出现.可以说决策单调性是斜率优化的前提. 斜率优化 \(D ...

  3. P3195 [HNOI2008]玩具装箱TOY 斜率优化dp

    传送门:https://www.luogu.org/problem/P3195 题目描述 P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京.他使用自己的压缩器进行压缩,其可以将任 ...

  4. 斜率优化DP复习笔记

    前言 复习笔记2nd. Warning:鉴于摆渡车是普及组题目,本文的难度定位在普及+至省选-. 参照洛谷的题目难度评分(不过感觉部分有虚高,提高组建议全部掌握,普及组可以选择性阅读.) 引用部分(如 ...

  5. CF 319C - Kalila and Dimna in the Logging Industry 斜率优化DP

    题目:伐木工人用电锯伐木,一共需要砍n棵树,每棵树的高度为a[i],每次砍伐只能砍1单位高度,之后需要对电锯进行充电,费用为当前砍掉的树中最大id的b[id]值.a[1] = 1 , b[n] = 0 ...

  6. [bzoj 2726] 任务安排 (斜率优化 线性dp)

    3月14日第三题!!!(虽然是15号发的qwq) Description 机器上有N个需要处理的任务,它们构成了一个序列.这些任务被标号为1到N,因此序列的排列为1,2,3-N.这N个任务被分成若干批 ...

  7. bzoj 1010 玩具装箱toy -斜率优化

    P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京.他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中.P教授有编号为1...N的N件玩具,第i件玩具 ...

  8. 【BZOJ3156】防御准备(动态规划,斜率优化)

    [BZOJ3156]防御准备(动态规划,斜率优化) 题面 BZOJ 题解 从右往左好烦啊,直接\(reverse\)一下再看题. 设\(f[i]\)表示第\(i\)个位置强制建立检查站时,前面都满足条 ...

  9. Bzoj1492: [NOI2007]货币兑换Cash(不单调的斜率优化)

    题面 传送门 Sol 题目都说了 必然存在一种最优的买卖方案满足: 每次买进操作使用完所有的人民币: 每次卖出操作卖出所有的金券. 设\(f[i]\)表示第\(i\)天可以有的最大钱数 枚举\(j&l ...

随机推荐

  1. 5大AR应用窥探移动未来~你见过吗?

    摘要:随着可穿戴设备的不断升温,尤其是Google Glass的出现,让AR技术再次走进我们的视线.以下尾随DevStore小编看看这5款优秀的AR应用,有木有闪到你的眼~ 眼下移动开发人员可选的AR ...

  2. 编写在浏览器中不弹出警告的ActiveX控件

    我们在编写ActiveX控件时,如果用在浏览器中,经常都会弹出现在运行的脚本不安全的提示, 如果给客户使用,将会带来极大不便.按照MSDN的介绍通常有两种一种是实现IObjectSafe接口,一种是通 ...

  3. Test oracle db iops

    Today, i need to test one database's iops and do something for oracle db's io test. How to test the ...

  4. cocos2dX 事件之触摸事件和触摸事件集合

    今天, 我们来学习cocos2dX里面的触摸事件与触摸事件合集, 如今的手机游戏交互基本上都是通过触摸交互的, 所以大家明确这节的重要性了吧, 本节篇幅比較大, 所以我就不扯闲话了 先来看看经常使用函 ...

  5. WKE——Webkit精简的纯C接口的浏览器

    以前不知道有这个东西 https://github.com/cexer/wke http://blog.csdn.net/weolar/article/details/50383491 http:// ...

  6. Practical Common Lisp

    Practical Common Lisp Practical Common Lisp

  7. jQuery 自学笔记—9 常见特效 (终章)

    隐藏.显示.切换,滑动,淡入淡出,以及动画 效果演示 点击这里,隐藏/显示面板 一寸光阴一寸金,因此,我们为您提供快捷易懂的学习内容. 在这里,您可以通过一种易懂的便利的模式获得您需要的任何知识. 实 ...

  8. Android 编程之第三方开发 MaoZhuaWeiBo微博开发演示样例-1

    在大学期间我做过非常多类似这种APP.这个是我们小组之前做的,我后期增加非常多新元素.完好了这个应用,由于为了加强 专业技术嘛.也是常常熬夜写些小东西,嘿嘿.只是还算不错.起码技术长进了不少嘛,还是非 ...

  9. NET之全平台一体化

    NET之全平台一体化的体验 一.前言 近来利用空闲时间研究了一下Xamarin的技术,想想既然提供了如此好的支持,就该尝试一切可能,来一个”大小通吃“. 何为全平台:APP包括Android.IOS. ...

  10. python语言学习6——python基础

    Python是一种计算机编程语言. 以#开头的语句是注释,注释是给人看的,可以是任意内容 其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块. Python程序是大小写敏感的,如果写错 ...