算法

使用单调队列优化dp 废话

对与一些dp的转移方程,我们可以通过拆使它与某个区间的最值相关。

这时可以用单调队列算出区间最值,进行优化。

例题

最大子段和

题意

给出一个长度为 \(n\) 的整数序列,从中找出一段长度不超过 \(m\) 的连续子序列,使得整个序列的和最大。

思路

设 \(sum_i\) 为 \(i\) 的前缀和,易得答案为:

\[\max_\limits{1\le i\le n}\{sum_i-\min_\limits{i-m\le k\le i-1}\{sum_k\}\}
\]

其中 \(\min_\limits{i-m\le k\le i-1}\{sum_k\}\) 这部分可以用单调队列快速求出。

那么算起来就变得简单多了。

代码

点击查看代码
  1. #include<bits/stdc++.h>
  2. #define _for(i,a,b) for(int i=a;i<=b;++i)
  3. #define for_(i,a,b) for(int i=a;i>=b;--i)
  4. #define ll long long
  5. using namespace std;
  6. const int N=3e5+10;
  7. int n,m,a[N],sum[N],f[N],ans;
  8. int q[N],h=1,t=0;
  9. inline int rnt(){
  10. int x=0,w=1;char c=getchar();
  11. while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
  12. while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return x*w;
  14. }
  15. void tmp(int k){
  16. while(h<=t&&q[h]<k-m)++h;
  17. ans=max(ans,sum[k]-sum[q[h]]);
  18. while(h<=t&&sum[q[t]]>sum[k])--t;
  19. q[++t]=k;
  20. }
  21. int main(){
  22. n=rnt(),m=rnt();
  23. _for(i,1,n){
  24. a[i]=rnt();
  25. sum[i]=sum[i-1]+a[i];
  26. tmp(i);
  27. }
  28. printf("%d\n",ans);
  29. return 0;
  30. }

修剪草坪

题意

给定一个 \(n\ (1\le n\le 10^5)\) 个元素的序列,可任选多个数,要求任意一段连续选的数长度不能超过 \(k\)。

\(1\le N\le 10^5\)

思路

设 \(f_{i,0/1}\) 表示第 \(i\) 个数选(1)或是不选(0),\(sum_i\) 表示 \(i\) 的前缀和。

转移方程为:

\[f_{i,0}=\max\{f_{i-1,0},f_{i-1,1}\}\\\
f_{i,1}=\max_\limits{i-k\le j<i}\{f_{j,0}+(sum_i-sum_j)\}
\]

\(f_{i,1}\) 转移方程可以拆成:

\[f_{i,1}=sum_i+\max_\limits{i-k\le j<i}\{f_{j,0}-sum_j\}
\]

用单调队列维护 \(f_{j,0}-sum_j\) 即可。

代码

点击查看代码
  1. #include<bits/stdc++.h>
  2. #define _for(i,a,b) for(ll i=a;i<=b;++i)
  3. #define for_(i,a,b) for(ll i=a;i>=b;--i)
  4. #define ll long long
  5. using namespace std;
  6. const ll N=3e5+10;
  7. ll n,k,a[N],sum[N],f[N][2],ans;
  8. ll q[N],h=1,t=0;
  9. inline ll rnt(){
  10. ll x=0,w=1;char c=getchar();
  11. while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
  12. while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return x*w;
  14. }
  15. void tmp(ll i){
  16. while(h<=t&&q[h]<i-k)++h;
  17. f[i][1]=f[q[h]][0]+sum[i]-sum[q[h]];
  18. while(h<=t&&f[q[t]][0]-sum[q[t]]<f[i][0]-sum[i])--t;
  19. q[++t]=i;
  20. }
  21. int main(){
  22. n=rnt(),k=rnt();
  23. tmp(0);
  24. _for(i,1,n){
  25. a[i]=rnt();
  26. sum[i]=sum[i-1]+a[i];
  27. f[i][0]=max(f[i-1][0],f[i-1][1]);
  28. tmp(i);
  29. }
  30. printf("%lld\n",max(f[n][0],f[n][1]));
  31. return 0;
  32. }

瑰丽华尔兹

题意

给出一个 \(N*M\) 的船上地图,有空地也有家具。

船上有 \(K\) 段时间,在每段时间都会往不同的方向倾斜,钢琴也会朝着那个方向倾斜,但不允许碰上家具。

求钢琴最长的滑行距离。

\(1\le N,M\le200,K\le200\)

思路

设 \(f_{i,j,k}\) 表示在第 \(i\) 段时间滑行到 \(j,k\) 的位置。

那么转移方程就是:

\[f_{i,j,k}=\max_\limits{(jj,kk)是i时刻可以滑到(j,k)的点}{f_{i,jj,kk}+|jj-j|+|kk-k|}
\]

瞎写的

按着倾斜的方向遍历,再用单调队列优化优化就好了。

代码

点击查看代码
  1. #include<bits/stdc++.h>
  2. #define _for(i,a,b) for(int i=a;i<=b;++i)
  3. #define for_(i,a,b) for(int i=a;i>=b;--i)
  4. #define ll long long
  5. using namespace std;
  6. const int N=210,inf=0x3f3f3f3f;
  7. int n,m,x,y,k,mp[N][N];
  8. int f[N][N][N],la[N][N][N],ans;
  9. inline ll rnt(){
  10. ll x=0,w=1;char c=getchar();
  11. while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
  12. while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return x*w;
  14. }
  15. inline char rch(){
  16. char c=getchar();
  17. while(c!='.'&&c!='x')c=getchar();
  18. return c;
  19. }
  20. struct dq{//deque
  21. int q[N],h,t;
  22. void nw(){
  23. memset(q,0,sizeof(q));
  24. h=1,t=0;
  25. }
  26. bool empty(){return h>t;}
  27. int front(){return q[h];}
  28. int back(){return q[t];}
  29. void pop_f(){++h;}
  30. void pop_b(){--t;}
  31. void push(int x){q[++t]=x;}
  32. };
  33. void dp(int d,int s,int t,int fx){
  34. int len=(t-s+1);
  35. if(fx==1){
  36. _for(j,1,m){
  37. dq q;q.nw();
  38. for_(i,n,1){
  39. if(mp[i][j]){
  40. while(!q.empty())q.pop_f();
  41. continue;
  42. }
  43. while(!q.empty()&&q.front()>i+len)q.pop_f();
  44. while(!q.empty()&&f[d-1][q.back()][j]+q.back()-i<f[d-1][i][j])q.pop_b();
  45. q.push(i);
  46. if(f[d-1][q.front()][j]>-1)
  47. f[d][i][j]=f[d-1][q.front()][j]+q.front()-i;
  48. ans=max(ans,f[d][i][j]);
  49. }
  50. }
  51. }
  52. else if(fx==2){
  53. _for(j,1,m){
  54. dq q;q.nw();
  55. _for(i,1,n){
  56. if(mp[i][j]){
  57. while(!q.empty())q.pop_f();
  58. continue;
  59. }
  60. while(!q.empty()&&q.front()<i-len)q.pop_f();
  61. while(!q.empty()&&f[d-1][q.back()][j]+i-q.back()<f[d-1][i][j])q.pop_b();
  62. q.push(i);
  63. if(f[d-1][q.front()][j]>-1)
  64. f[d][i][j]=f[d-1][q.front()][j]+i-q.front();
  65. ans=max(ans,f[d][i][j]);
  66. }
  67. }
  68. }
  69. else if(fx==3){
  70. _for(i,1,n){
  71. dq q;q.nw();
  72. for_(j,m,1){
  73. if(mp[i][j]){
  74. while(!q.empty())q.pop_f();
  75. continue;
  76. }
  77. while(!q.empty()&&q.front()>j+len)q.pop_f();
  78. while(!q.empty()&&f[d-1][i][q.back()]+q.back()-j<f[d-1][i][j])q.pop_b();
  79. q.push(j);
  80. if(f[d-1][i][q.front()]>-1)
  81. f[d][i][j]=f[d-1][i][q.front()]+q.front()-j;
  82. ans=max(ans,f[d][i][j]);
  83. }
  84. }
  85. }
  86. else{
  87. _for(i,1,n){
  88. dq q;q.nw();
  89. _for(j,1,m){
  90. if(mp[i][j]){
  91. while(!q.empty())q.pop_f();
  92. continue;
  93. }
  94. while(!q.empty()&&q.front()<j-len)q.pop_f();
  95. while(!q.empty()&&f[d-1][i][q.back()]+j-q.back()<f[d-1][i][j])q.pop_b();
  96. q.push(j);
  97. if(f[d-1][i][q.front()]>-1)
  98. f[d][i][j]=f[d-1][i][q.front()]+j-q.front();
  99. ans=max(ans,f[d][i][j]);
  100. }
  101. }
  102. }
  103. }
  104. int main(){
  105. n=rnt(),m=rnt(),x=rnt(),y=rnt(),k=rnt();
  106. _for(i,1,n)
  107. _for(j,1,m)
  108. mp[i][j]=(bool)(rch()=='x');
  109. memset(f,-inf,sizeof(f));
  110. f[0][x][y]=0;
  111. _for(i,1,k){
  112. int s=rnt(),t=rnt(),fx=rnt();
  113. dp(i,s,t,fx);
  114. }
  115. printf("%d\n",ans);
  116. return 0;
  117. }

股票交易

题意

一共有 \(T\) 天,知道了每天股票的买入金额 \(ap_i\),卖出金额 \(bp_i\),买入限制 \(as_j\),卖出限制 \(bs_j\)。要求持有股票数不能超过 \(MaxP\) ,两次交易相隔 \(w\) 天。

\(1\le w<T\le 2*10^3,1\le MaxP\le 2*10^3\)

思路

设 \(f_{i,j}\) 表示第 \(i\) 天持有 \(j\) 个股票的最大钱数。

有四种情况:

  • 凭空买

    转移方程:
\[f_{i,j}=-ap_i*j
\]
  • 不买不卖

    转移方程:
\[f_{i,j}=\max\{f_{i,j},f_{i-1,j}\}
\]
  • 只买

    转移方程:
\[f_{i,j}=\max\{f_{i,j},\max_\limits{j-as_i\le k\le j}\{f_{i-w-1,k}-(j-k)*ap_i\}\}
\]
  • 只卖

    转移方程:
\[f_{i,j}=\max\{f_{i,j},\max_\limits{j\le k\le j+bs_i}\{f_{i-w-1,k}+(k-j)*bp_i\}\}
\]

此时时间复杂度是 \(O(T*MaxP^2)\)。

观察只买和只卖的情况,可以发现它们的转移方程可以用单调队列优化掉一维。

那么时间复杂度就被优化成了 \(O(T*MaxP)\),完全可过。

代码

点击查看代码
  1. #include<bits/stdc++.h>
  2. #define _for(i,a,b) for(ll i=a;i<=b;++i)
  3. #define for_(i,a,b) for(ll i=a;i>=b;--i)
  4. #define ll long long
  5. using namespace std;
  6. const ll N=4010,inf=0x3f3f3f3f;
  7. ll t,mxp,w,ap[N],bp[N],as[N],bs[N];
  8. inline ll rnt(){
  9. ll x=0,w=1;char c=getchar();
  10. while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
  11. while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
  12. return x*w;
  13. }
  14. struct dq{//deque
  15. ll q[N],h,t;
  16. void nw(){
  17. memset(q,0,sizeof(q));
  18. h=1,t=0;
  19. }
  20. bool empty(){return h>t;}
  21. ll front(){return q[h];}
  22. ll back(){return q[t];}
  23. void pop_f(){++h;}
  24. void pop_b(){--t;}
  25. void push(int x){q[++t]=x;}
  26. };
  27. namespace SOLVE{
  28. ll f[N][N];
  29. void DpPkm(ll i){//凭空买
  30. _for(j,0,min(mxp,as[i]))
  31. f[i][j]=-ap[i]*j;
  32. return;
  33. }
  34. void DpBmbm(ll i){//不买也不卖
  35. _for(j,0,mxp)
  36. f[i][j]=max(f[i][j],f[i-1][j]);
  37. return;
  38. }
  39. void DpBuy(ll i){//原基础上买来
  40. dq q;q.nw();
  41. _for(j,0,mxp){
  42. while(!q.empty()&&j-q.front()>as[i])q.pop_f();
  43. while(!q.empty()&&f[i-w-1][j]>f[i-w-1][q.back()]-(j-q.back())*ap[i])q.pop_b();
  44. q.push(j);
  45. f[i][j]=max(f[i][j],f[i-w-1][q.front()]-(j-q.front())*ap[i]);
  46. }
  47. return;
  48. }
  49. void DpSell(ll i){//原基础上卖出
  50. dq q;q.nw();
  51. for_(j,mxp,0){
  52. while(!q.empty()&&q.front()-j>bs[i])q.pop_f();
  53. while(!q.empty()&&f[i-w-1][j]>f[i-w-1][q.back()]+(q.back()-j)*bp[i])q.pop_b();
  54. q.push(j);
  55. f[i][j]=max(f[i][j],f[i-w-1][q.front()]+(q.front()-j)*bp[i]);
  56. }
  57. return;
  58. }
  59. ll Solve(){
  60. memset(f,-inf,sizeof(f));
  61. _for(i,1,t){
  62. DpPkm(i);
  63. DpBmbm(i);
  64. if(i-w>1){
  65. DpBuy(i);
  66. DpSell(i);
  67. }
  68. }
  69. return f[t][0];
  70. }
  71. }
  72. int main(){
  73. t=rnt(),mxp=rnt(),w=rnt();
  74. _for(i,1,t)
  75. ap[i]=rnt(),bp[i]=rnt(),as[i]=rnt(),bs[i]=rnt();
  76. printf("%lld\n",SOLVE::Solve());
  77. return 0;
  78. }/*
  79. */

$$
\Huge{\mathfrak{The\ End}}
$$

「学习笔记」单调队列优化dp的更多相关文章

  1. 「学习笔记」FFT 之优化——NTT

    目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...

  2. bzoj1499: [NOI2005]瑰丽华尔兹&&codevs1748 单调队列优化dp

    这道题 网上题解还是很多很好的 强烈推荐黄学长 码风真的好看 神犇传送门 学习学习 算是道单调队列优化dp的裸题吧 #include<cstdio> #include<cstring ...

  3. 「单调队列优化DP」P2034 选择数字

    「单调队列优化DP」P2034 选择数字 题面描述: 给定一行n个非负整数a[1]..a[n].现在你可以选择其中若干个数,但不能有超过k个连续的数字被选择.你的任务是使得选出的数字的和最大. 输入格 ...

  4. 【笔记篇】单调队列优化dp学习笔记&&luogu2569_bzoj1855股票交♂易

    DP颂 DP之神 圣洁美丽 算法光芒照大地 我们怀着 崇高敬意 跪倒在DP神殿里 你的复杂 能让蒟蒻 试图入门却放弃 在你光辉 照耀下面 AC真心不容易 dp大概是最经久不衰 亘古不化的算法了吧. 而 ...

  5. 算法笔记--单调队列优化dp

    单调队列:队列中元素单调递增或递减,可以用双端队列实现(deque),队列的前面和后面都可以入队出队. 单调队列优化dp: 问题引入: dp[i] = min( a[j] ) ,i-m < j ...

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

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

  7. Parade(单调队列优化dp)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2490 Parade Time Limit: 4000/2000 MS (Java/Others)    ...

  8. 1855: [Scoi2010]股票交易[单调队列优化DP]

    1855: [Scoi2010]股票交易 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1083  Solved: 519[Submit][Status] ...

  9. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

随机推荐

  1. Maven POM文件介绍

    1. POM文件是什么 1.1 Super POM 1.2 Minimal POM 1.3 Effective POM 3. 项目继承 和 项目聚合 2.1 Project Inheritance 项 ...

  2. NLog自定义Target之MQTT

    NLog是.Net中最流行的日志记录开源项目(之一),它灵活.免费.开源 官方支持文件.网络(Tcp.Udp).数据库.控制台等输出 社区支持Elastic.Seq等日志平台输出 实时日志需求 在工业 ...

  3. 实现领域驱动设计 - 使用ABP框架 - 什么是领域驱动设计?

    前言: 最近看到ABP官网的一本电子书,感觉写的很好,翻译出来,一起学习下 (Implementing Domain Driven Design) https://abp.io/books DDD简介 ...

  4. go-zero微服务实战系列(七、请求量这么高该如何优化)

    前两篇文章我们介绍了缓存使用的各种最佳实践,首先介绍了缓存使用的基本姿势,分别是如何利用go-zero自动生成的缓存和逻辑代码中缓存代码如何写,接着讲解了在面对缓存的穿透.击穿.雪崩等常见问题时的解决 ...

  5. sql-关键词的大小写与注释

    是否区分大小写 和 注释 大小写 oracle 自带的sqlplus: mysql 客户端 : Navicat: 注释 oracle 自带的sqlplus: mysql 客户端 : 小节 oracle ...

  6. 前端下载图片的N种方法

    前几天一个简单的下载图片的需求折腾了我后端大佬好几天,最终还是需要前端来搞,开始说不行的笔者最后又行了,所以趁着这个机会来总结一下下载图片到底有多少种方法. 先起个服务 使用expressjs起个简单 ...

  7. 不存在的!python说不给数据的浏览器是不存在的!

    有时候我们些代码是总发此疑惑? 为什么别人采集 xx 网站的时候能成功,而我却总是不返回给数据出现这种原因时往往是我们没有给够伪装, 被识别了出来~ 就像人,你出门肯定是要穿衣服的对不,如果你不穿! ...

  8. python实现人脸关键部位检测(附源码)

    人脸特征提取 本文主要使用dlib库中的人脸特征识别功能. dlib库使用68个特征点标注出人脸特征,通过对应序列的特征点,获得对应的脸部特征.下图展示了68个特征点.比如我们要提 取眼睛特征,获取3 ...

  9. Java开发学习(七)----DI依赖注入之自动装配与集合注入

    一.自动配置 上一篇博客花了大量的时间把Spring的注入去学习了下,总结起来就两个字麻烦.麻烦在配置文件的编写配置上.那有更简单方式么?有,自动配置 1.1 依赖自动装配 IoC容器根据bean所依 ...

  10. E: Problem executing scripts APT::Update::Post-Invoke-Success 'if /usr/bin/t

    sudo apt-get remove libappstream3