可以用队列优化或斜率优化的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也是没用的。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <algorithm>
  5. #include <iostream>
  6. #include <queue>
  7. #include <stack>
  8. #include <vector>
  9. #include <map>
  10. #include <set>
  11. #include <string>
  12. #include <math.h>
  13. using namespace std;
  14. #pragma warning(disable:4996)
  15. #pragma comment(linker, "/STACK:1024000000,1024000000")
  16. typedef long long LL;
  17. const int INF = <<;
  18. /*
  19. */
  20. const int N = + ;
  21. int a[N];
  22. int dp[N];
  23. int q[N], head, tail;
  24. int main()
  25. {
  26. int n, m;
  27. while (scanf("%d%d", &n,&m) != EOF)
  28. {
  29. for (int i = ; i <= n; ++i)
  30. scanf("%d", &a[i]);
  31. head = tail = ;
  32. q[tail++] = ;
  33. dp[] = a[];
  34. for (int i = ; i <= n; ++i)
  35. {
  36. while (head < tail && a[i] < a[q[tail - ]])//插入新的元素,要使得队列依旧单调递增
  37. tail--;
  38. q[tail++] = i;
  39. while (head < tail && q[head] < i - m + )//剔除不合要求的pos
  40. head++;
  41. dp[i] = a[q[head]];
  42. }
  43. for (int i = ; i <= n; ++i)
  44. printf("%d ", dp[i]);
  45. puts("");
  46. /*
  47. 5 3
  48. 1 2 3 4 5
  49. 1 1 1 2 3
  50. */
  51. }
  52. return ;
  53. }

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

需要注意到,上面要求可选的决策集是连续的。同时也可以注意到,当前决策所需要的值是不受现在的状态所影响的,即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]是递增的,所以前面判断过的点不用再次判断。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <algorithm>
  5. #include <iostream>
  6. #include <queue>
  7. #include <stack>
  8. #include <vector>
  9. #include <map>
  10. #include <set>
  11. #include <string>
  12. #include <math.h>
  13. using namespace std;
  14. #pragma warning(disable:4996)
  15. #pragma comment(linker, "/STACK:1024000000,1024000000")
  16. typedef long long LL;
  17. const int INF = <<;
  18. /*
  19. 普通的dp需要遍历以前的所有值,
  20. 斜率dp就是通过舍弃一些值,必须是当前可舍弃,以后也是可舍弃的值,从而减少遍历量
  21. 或者是使得队首的元素就是最优的,
  22.  
  23. 关键是如何判断可舍弃,。。。。。通过数学分析斜率来舍弃???
  24.  
  25. 第i个字符肯定接在前面j个字符的后面,或者另起一行,但是费用该怎么算呢
  26. dp[i][1]表示1另起一行 ,那么费用是 dp[i][1] = ci^2 + m + min(dp[i-1][0],dp[i-1][1])
  27. dp[i][0] 表示接在前面j个字符的后面的最小费用 sum{cost[j]}^2+m + min(dp[j-1][0],dp[j-1][1])
  28.  
  29. */
  30.  
  31. const int N = + ;
  32. int sum[N];
  33. int dp[N];
  34. int q[N], head, tail;
  35. int getUp(int i, int j)
  36. {
  37. return dp[i] + sum[i] * sum[i] - (dp[j] + sum[j] * sum[j]);
  38. }
  39. int getDown(int i, int j)
  40. {
  41. return * sum[i] - * sum[j];
  42. }
  43. int main()
  44. {
  45. int n, m;
  46. while (scanf("%d%d", &n, &m) != EOF)
  47. {
  48. for (int i = ; i <= n; ++i)
  49. {
  50. scanf("%d", &sum[i]);
  51. sum[i] += sum[i - ];
  52. }
  53. head = tail = ;
  54. q[tail++] = ;
  55. for (int i = ; i <= n; ++i)
  56. {
  57. /*
  58.  
  59. */
  60. while (head + < tail && getUp(q[head + ], q[head]) <= sum[i] * getDown(q[head + ], q[head ]))
  61. head++;
  62. dp[i] = (sum[i] - sum[q[head]]) * (sum[i] - sum[q[head]]) + m + dp[q[head]];
  63. while (head + < tail && getUp(i, q[tail - ])*getDown(q[tail - ], q[tail - ]) <= getUp(q[tail - ], q[tail - ])*getDown(i, q[tail - ]))
  64. tail--;
  65. q[tail++] = i;
  66. }
  67. printf("%d\n", dp[n]);
  68. }
  69. return ;
  70. }

总结,

当横坐标递增,斜率递增时,用队列维护,可以在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. Java_io体系之BufferedWriter、BufferedReader简介、走进源码及示例——16

    Java_io体系之BufferedWriter.BufferedReader简介.走进源码及示例——16 一:BufferedWriter 1.类功能简介: BufferedWriter.缓存字符输 ...

  2. OCA读书笔记(7) - 管理数据库存储结构

    7.Managing Database Storage Structures 逻辑结构 数据库的存储结构有物理结构和逻辑结构组成的 物理结构:物理上,oracle是由一些操作系统文件组成的 SQL&g ...

  3. Servlet和JSP读书笔记(三)之Cookie

    一. 浏览器和服务器之间通信的简单介绍引出Cookie和Session(只是简单的简介,不包含协议方面的知识) 1.当我们在浏览器中输入一个地址后,回车后就可以看到浏览器给我们展示的漂亮页面.在这个过 ...

  4. Qt多线程(有详细例子)

    Qt线程类 Qt 包含下面一些线程相关的类:QThread 提供了开始一个新线程的方法QThreadStorage 提供逐线程数据存储QMutex  提供相互排斥的锁,或互斥量QMutexLocker ...

  5. cocos2dx之lua项目开发中MVC框架的简单应用

    **************************************************************************** 时间:2015-03-31 作者:Sharin ...

  6. vim高级编辑(一)

    本文出自   http://blog.csdn.net/shuangde800 ------------------------------------------------------------ ...

  7. 第四题(迅雷笔试题):编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。

    #include <iostream> #include <stdlib.h> #include <pthread.h> using namespace std; ...

  8. [IDEs]Eclipse设置花括号样式

    用惯Vistual Studio,在使用Eclipse时发现有很多东西还是挺不习惯,第一个就要解决花括号的样式 步骤: 1.Windows->Preferences->Java->C ...

  9. 一个通用onReady函数的实现

    define([], function(){ function onReady(fn) { var DOC = document, html = DOC.documentElement, W3C = ...

  10. 知识网之C++总结

    米老师常说的一句话:构造知识网. 立即要考试了.就让我们构造一下属于C++的知识网.首先从总体上了解C++: 从图中能够了解到,主要有五部分.而当我们和之前的知识联系的话,也就剩下模板和运算符重载以及 ...