
【DP】例解单调队列优化
发布日期:2021-05-09 00:23:54
浏览次数:22
分类:原创文章
本文共 2961 字,大约阅读时间需要 9 分钟。
upd: 2021.4.28 添加滑动窗口模板供参考。
目录
简介
滑动窗口模板
例题
简介
单调队列是如何优化DP的呢?其实就是推柿子(找到状态转移方程),然后借助柿子里面连续的特征,利用单调队列将查询最值的代价变低,进而达到优化的目的。
滑动窗口模板
模板题传送门:
代码中的单调队列是双端队列实现的,这里说一下用手写双端队列的基本操作:
检验队列非空:tt>=hh
弹出队头:hh++
弹出队尾:tt--
插入队尾:v[++tt]=插入值
代码:
#include<bits/stdc++.h>using namespace std;const int N=1e6+5;int q[N], tt=-1, hh=0;int w[N];int main(){ int n, k; cin>>n>>k; for(int i=1; i<=n; i++) cin>>w[i]; for(int i=1; i<=n; i++){ while(tt>=hh && q[hh]+k<=i) hh++; while(tt>=hh && w[i]<=w[q[tt]]) tt--; q[++tt]=i; if(i>=k) cout<<w[q[hh]]<<' '; } cout<<endl; tt=-1, hh=0; for(int i=1; i<=n; i++){ while(tt>=hh && q[hh]+k<=i) hh++; while(tt>=hh && w[i]>=w[q[tt]]) tt--; q[++tt]=i; if(i>=k) cout<<w[q[hh]]<<' '; } cout<<endl; return 0;}
例题
例1:
传送门:
分析:\(f[i]\) 表示前 \(i\) 个烽火台的最小代价,其中第 \(i\) 个是点燃的。
那么我们便得到状态转移方程: \(f[i]=\min(f[i-1],...,f[i-m+1])+w[i]\)
如果直接利用上面的方程进行DP,那么复杂度是 \(O(N^2)\) ,自然是超时的。
注意到随着 \(i\) 的连续变化,\(\min\) 所包含的区间也是连续变化的,即写下 \(i,i+1\) 的情况,\(\min\) 所包括的区间也偏移一个单位:
\[f[i]=\min(f[i-1],...,f[i-m+1])+w[i]\]
\[f[i+1]=\min(f[i],f[i-1],...,f[i-m+2])+w[i+1]\]
于是我们可以利用单调队列的思想来维护这个 \(\min\) 。
代码:
#include<bits/stdc++.h>using namespace std;const int N=2e5+5;int f[N], w[N];int n, m;int q[N];int main(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>w[i]; int tt=0, hh=0; for(int i=1;i<=n;i++){ while(tt>=hh && i-q[hh]>m) hh++; // upd f[] f[i]=f[q[hh]]+w[i]; while(tt>=hh && f[i]<=f[q[tt]]) tt--; q[++tt]=i; } int ans=0x3f3f3f3f; for(int i=n-m+1; i<=n; i++) ans=min(ans, f[i]); cout<<ans<<endl; return 0;}
例2:
传送门:
分析:\(f[i]\) 表示在前 \(i\) 个奶牛中合法选取方案的最大贡献。
分情况讨论:
- 第 \(i\) 个奶牛没有选取,那么 \(f[i]=f[i-1]\)
- 否则,选取了包括 \(i\) 以及前面的奶牛总共连续 \(j\) 头。可以得到转移方程: \(f[i]=\max(f[i-j-1]-s[i-j])+s[i]\) ,其中 \(j \in [1,k]\) ( \(k\) 为题目允许连续的最大数量)
那么我们拿单调队列维护一下 \(v[i]=f[i-1]-s[i]\) ,这样就能做到 \(O(1)\) 查询最值了。
代码:
#include<bits/stdc++.h>using namespace std;typedef long long ll;const int N=1e5+5;int w[N];ll f[N], v[N], s[N];int q[N];int main(){ int n, k; cin>>n>>k; for(int i=1;i<=n;i++) cin>>w[i], s[i]=s[i-1]+w[i]; f[0]=0; int tt=0, hh=0; for(int i=1;i<=n;i++){ while(tt>=hh && q[hh]+k<i) hh++; f[i]=max(v[q[hh]]+s[i], f[i-1]); v[i]=f[i-1]-s[i]; while(tt>=hh && v[i]>=v[q[tt]]) tt--; q[++tt]=i; } cout<<f[n]<<endl; return 0;}
例3:
传送门:
分析:二分一下两个题目之间的长度 \(len\) ,然后对于这样的 \(len\) ,看看能不能做到选取最小代价的合法(即距离不超过 \(len\))物品即可。(除了二分外与例1完全类似)
代码:
#include<bits/stdc++.h>using namespace std;const int N=50005;int n, t;int w[N];int f[N];int q[N];bool check(int len){ int tt=0, hh=0; f[0]=0; int sze=len+1; // the size of the window for(int i=1;i<=n;i++){ while(tt>=hh && q[hh]+sze<i) hh++; f[i]=f[q[hh]]+w[i]; while(tt>=hh && f[i]<=f[q[tt]]) tt--; q[++tt]=i; } int res=0x3f3f3f3f; for(int i=n-sze+1; i<=n; i++) res=min(res, f[i]); return res<=t;}int main(){ cin>>n>>t; for(int i=1;i<=n;i++) cin>>w[i]; int l=1, r=N; while(l<r){ int mid=l+r>>1; if(check(mid)) r=mid; else l=mid+1; } cout<<l<<endl; return 0;}
发表评论
最新留言
留言是一种美德,欢迎回访!
[***.207.175.100]2025年05月18日 18时58分41秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Map如何获取所有value的值
2025-04-12
Map存入的数据丢失类型任意
2025-04-12
Map排序
2025-04-12
map格式和string格式转化为json格式
2025-04-12
Map的深浅拷贝的探究
2025-04-12
Map的遍历方式
2025-04-12
map遍历测试结果
2025-04-12
Map集合
2025-04-12
Map集合循环遍历的几种方式
2025-04-12
Map(关联式容器)
2025-04-12
margin在块元素、内联元素中的区别 padding
2025-04-12
Mariadb 分表
2025-04-12
MariaDB密码重置
2025-04-12
MariaDB的简单使用
2025-04-12
Mariadb第一章:介绍及安装--小白博客
2025-04-12
Mark Mind:下一代思维导图编辑器
2025-04-12
Markdown
2025-04-12
markdown
2025-04-12
Markdown —— 背景色
2025-04-12