单调队列单调栈和优化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)

这个题和烽火传递还有滑动窗口很像。

只是对于 i[l,n],dp[i]=max(dp[ir],dp[il])+a[i]

所以,在循环中,我们设定两个变量(差值为 l)去实现对于当前循环的 i ,从 [ir,il] 的状态转移。

代码:

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)

但是这个题和上面的很像。但是第一遍要倒序 第一遍要倒序 第一遍要倒序

第一遍先找区间 [i,n] 内的前缀和最小值,第二遍找区间 [1,i1] 内的前缀和最小值,如果都满足条件:0 ,计数器加一。

致敬我上午条代码的半个小时。。

为什么我的线段树被卡了 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

模板。题目中说,

对于奶牛 i,如果奶牛 j 满足 i<jHi<Hj,我们可以说奶牛 i 可以仰望奶牛 j。 求出每只奶牛离她最近的仰望对象。

我们考虑倒序。对于 i[1,n],要找到一个 j[1,i),j>i 并且 ij 最小。

那么我们可以维护一个数据结构 q,使得在每一位上,都有 q.top>i ,那么我们在将数据传入 q 中时,使 q.top>i,再每次记录答案 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 ,对于第 i[1,n] 个牛,有两种状态:选与不选。

  • 不选:

那么这一头牛对答案就没有贡献,显然

dp[i][0]=max(dp[i1][0],dp[i1][1])

  • 选:

这个有点难搞。考虑对于 i[1,n],如果要在这个点上取得最大值,那么在 j[ik,i) 的区间内,一定有一个 j 不选,其他在 (j,i) 区间内的牛必须选,可以使用前缀和 s 数组。

我们可以得到:

dp[i][1]=max(dp[j][0]s[j]+s[i]),j[ik,i)

又因为 s[i] 对于每个 i,可以认为是定值,那么就可以使用单调队列维护区间 [ik,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)

想不出用单调队列解决的方法,我在题解区看到了一个好办法: 区间伸缩,就是 尺取法

尺取法的思路是这样的:

类似于双指针,我们使用两个变量 lr 分别带表当前区间的端点。

  • 当此区间不符合要求时,我们使 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;
}

斜率

放一下,到年后再搞。

posted @   ccjjxx  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 易语言 —— 开山篇
点击右上角即可分享
微信分享提示