单调队列单调栈和优化dp学习笔记
单队+斜率
一、单队
原理:在动态规划问题中,要求区间最值,便可以维护一个单调队列,使得时间复杂度降低。
单调队列模板:
int tt=1,hh=1; q[1]=a[1]; for(int i=1;i<=n;i++) { while(hh<=tt&&dp[q[tt]]>=dp[i-1])tt--;//弹出队尾元素 // while(i-q[tt]>=m)++tt; q[++tt]=i-1;//加入现在的元素,至于为什么是 i-1 而不是 i,我也没明白 if(i-q[hh]>m)hh++;//保持最小值在当前区间内 dp[i]=dp[q[hh]]+a[i];//dp 状态转移 if(i>n-m)ans=min(ans,dp[i]);//更新答案 }
这是 T182. 「一本通 5.5 练习 1」烽火传递 的代码,可以参考。
T182. 「一本通 5.5 练习 1」烽火传递
模板题。主要是熟悉代码和熟悉变更。
#include<bits/stdc++.h> using namespace std; int m,n,a[200010]; int dp[200010]; int q[200100]; int main() { cin>>n>>m;int ans=INT_MAX; for(int i=1;i<=n;i++) cin>>a[i]; // dp[1]=a[1]; int tt=1,hh=1; q[1]=a[1]; for(int i=1;i<=n;i++) { while(hh<=tt&&dp[q[tt]]>=dp[i-1])tt--; // while(i-q[tt]>=m)++tt; q[++tt]=i-1; if(i-q[hh]>m)hh++; dp[i]=dp[q[hh]]+a[i]; if(i>n-m)ans=min(ans,dp[i]); } // for(int i=1;i<=n;i++)cout<<dp[i]<<" "; // for(int i=n-m+1;i<=n;i++)ans=min(ans,dp[i]); cout<<ans; }
P2331. 「一本通 5.5 例 2」最大连续和
模板题 +1。维护一个前缀和 s[i]
,
让答案从头开始取单调队列顶端元素,即维护的前缀和最大值到当前位置的前缀和。
从头开始 从头开始 从头开始 从头开始 从头开始 从头开始
P1725 琪露诺(luogu)
这个题和烽火传递还有滑动窗口很像。
只是对于 。
所以,在循环中,我们设定两个变量(差值为 )去实现对于当前循环的 ,从 的状态转移。
代码:
cin>>n>>l>>r;int ans=-INT_MAX-1; for(int i=0;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++)dp[i]=-1145141919;//要赋初值为极小值 dp[0]=0;//dp的边界 int tt=1,hh=1;//单队的首尾指针 int p=0;//这个p就是核心。p=i-l for(int i=l;i<=n;i++) { while(hh<=tt&&dp[q[tt]]<=dp[p])tt--;//窗口起点在p // while(i-q[tt]>=m)++tt; q[++tt]=p;//入队也要入p while(q[hh]+r<i)hh++; dp[i]=dp[q[hh]]+a[i]; if(i>=n-r+1) ans=max(ans,dp[i]); p++; }
P1714 切蛋糕(luogu)
简单题。似曾相识。其实就是P2331. 「一本通 5.5 例 2」最大连续和。。。
P2629 好消息,坏消息(luogu)
但是这个题和上面的很像。但是第一遍要倒序 第一遍要倒序 第一遍要倒序。
第一遍先找区间 内的前缀和最小值,第二遍找区间 内的前缀和最小值,如果都满足条件: ,计数器加一。
致敬我上午条代码的半个小时。。
为什么我的线段树被卡了 3 个点??题解里的却 AC 了?
for(int i=n;i>=1;i--) { while(hh<=tt&&s[q[tt]]>=s[i])tt--; q[++tt]=i; while(q[hh]<i)hh++; if(s[q[hh]]-s[i-1]<0)f[i]=1; } for(int i=1;i<=n;i++) { while(hh<=tt&&s[q[hh]]>=s[i])tt--; q[++tt]=i; if(s[n]-s[i-1]+s[q[hh]]>=0&&!f[i])cnt++; }
单调队列的变种:单调栈
P2947 [USACO09MAR] Look Up S
模板。题目中说,
对于奶牛 ,如果奶牛 满足 且 ,我们可以说奶牛 可以仰望奶牛 。 求出每只奶牛离她最近的仰望对象。
我们考虑倒序。对于 ,要找到一个 并且 最小。
那么我们可以维护一个数据结构 ,使得在每一位上,都有 ,那么我们在将数据传入 中时,使 ,再每次记录答案 q[hh]
就好了。
代码:
#include<bits/stdc++.h> using namespace std; int n,a[100001],q[100001],ans[100001]; int main() { cin>>n; int hh=0; for(int i=1;i<=n;i++)cin>>a[i]; for(int i=n;i>=1;i--) { while(hh>0&&a[q[hh]]<=a[i]) hh--; ans[i]=q[hh]; q[++hh]=i; } for(int i=1;i<=n;i++) cout<<ans[i]<<endl; }
P286. [USACO Open11] 修剪草坪
有点小难,但还好,多亏了玮子。
单调队列不如硬 D,也不如优先队列。
——wmw
考虑硬 D ,对于第 个牛,有两种状态:选与不选。
- 不选:
那么这一头牛对答案就没有贡献,显然
- 选:
这个有点难搞。考虑对于 ,如果要在这个点上取得最大值,那么在 的区间内,一定有一个 不选,其他在 区间内的牛必须选,可以使用前缀和 s
数组。
我们可以得到:
又因为 s[i]
对于每个 ,可以认为是定值,那么就可以使用单调队列维护区间 的 dp[j][0]-s[j]
。
代码:
for(int i=1;i<=n;i++) { dp[i][0]=max(dp[i-1][0],dp[i-1][1]); while(q[hh]<i-k)++hh; dp[i][1]=dp[q[hh]][0]+s[i]-s[q[hh]]; while(hh<=tt&&dp[q[tt]][0]<dp[i][0]) tt--; q[++tt]=i; } cout<<max(dp[n][1],dp[n][0]);
P1638 逛画展(luogu)
想不出用单调队列解决的方法,我在题解区看到了一个好办法: 区间伸缩,就是 尺取法。
尺取法的思路是这样的:
类似于双指针,我们使用两个变量 l
和 r
分别带表当前区间的端点。
-
当此区间不符合要求时,我们使
r++
,扩大区间。 -
当此区间符合要求时,我们使
l++
,缩小区间。
循环此操作直到 r==n
结束,此时找到的最小值即是答案。
while(l<=r&&r<=n) { if(cnt==m) { if(ans>r-l+1) ans=r-l+1,ansa=l,ansb=r;//统计答案 b[a[l]]--;//缩小区间 if(b[a[l]]==0)cnt--; l++; } else { r++;b[a[r]]++;//扩大区间 if(b[a[r]]==1)cnt++;//判断当前左端点的贡献 } } cout<<ansa<<" "<<ansb;
P330. [SCOI2009] 生日礼物
我使用的思路也是区间伸缩。输入的时候离散化一下,就可以继续使用区间伸缩
。
#include<bits/stdc++.h> using namespace std; int n,k,cnt; int ans=INT_MAX; int b[1000010]; struct emw{ int pos,val; bool operator <(const emw &l)const return pos<l.pos; }a[1000100]; int main() { cin>>n>>k; for(int i=1;i<=k;i++) { int s;cin>>s; for(int j=1;j<=s;j++) { cin>>a[++cnt].pos,a[cnt].val=i; } } sort(a+1,a+1+cnt); int l=1,r=1,c=1;b[a[1].val]=1; for(int i=2;i<=n;i++) if(a[i].pos==a[i-1].pos) { r++,b[a[i].val]++; if(b[a[i].val]==1)++c; } else break; while(l<=r&&r<=n) { if(c==k) { ans=min(ans,a[r].pos-a[l].pos); b[a[l].val]--; if(b[a[l].val]==0)c--; l++; if(l>n)break; while(a[l].pos==a[l-1].pos) { b[a[l].val]--; if(b[a[l].val]==0)c--; l++;if(l>n)break; } } else { r++; if(r>n)break; b[a[r].val]++; if(b[a[r].val]==1) c++; while(a[r+1].val==a[r].val) { r++;if(r>n)break; b[a[r].val]++; if(b[a[r].val]==1)c++; } } } cout<<ans; }
斜率
放一下,到年后再搞。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 易语言 —— 开山篇