单调队列以及单调队列优化DP
单调队列定义:
其实单调队列就是一种队列内的元素有单调性的队列,因为其单调性所以经常会被用来维护区间最值或者降低DP的维数已达到降维来减少空间及时间的目的。
单调队列的一般应用:
1.维护区间最值
2.优化DP
例题引入:
求m区间内的最小值:https://www.luogu.org/problemnew/show/P1440
一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。
例题解答:
首先看到题目可以很快想到O(NM),对于2*10^6这样的数据无疑要TLE的;
接下来考虑用单调队列,因为每一个答案只与当前下标的前m个有关,所以可以用单调队列维护前m的个最小值,
考虑如何实现该维护的过程??
显然当前下标X的m个以前的元素(即下标小于X-M+1的元素)肯定对答案没有贡献,所以可以将其从单调队列中删除。
对于两个元素A,B,下标分别为a,b,如果有A>=B&&a<b那么B留在队列里肯定优于A,因此可以将A删除。
维护队首:如果队首已经是当前元素的m个之前,将head++,弹出队首元素
维护队尾:比较q[tail]与当前元素的大小,若当前元素更优tail++,弹出队尾元素,直到可以满足队列单调性后加入当前元素。
考虑单调队列的时间复杂度:由于每一个元素只会进队和出队一次,所以为O(N)。
一般建议用数组模拟单调队列进行操作,而不用系统自带的容器,因为系统自带容器不易调试且可能有爆空间的危险。
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define INF 0x3f3f3f3f
#define ll long long
#define maxn 2000009
#define maxm
inline ll read()
{
ll x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-') f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=(x<<)+(x<<)+(ll)(ch-'');ch=getchar();}
return x*f;
}
int n,m,k,tot,head,tail;
int a[maxn],q[maxn];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();
for(int i=;i<=n;i++)
a[i]=read();
head=,tail=;//起始位置为1 因为插入是q[++tail]所以要初始化为0
for(int i=;i<=n;i++)//每次队首的元素就是当前的答案
{
printf("%d\n",a[q[head]]);
while(i-q[head]+>m&&head<=tail)//维护队首
head++;
while(a[i]<a[q[tail]]&&head<=tail)//维护队尾
tail--;
q[++tail]=i;
}
// fclose(stdin);
// fclose(stdout);
return ;
}
习题报告:
滑动窗口:https://www.luogu.org/problemnew/show/P1886
解题思路: 此题与例题相同,只是所要求的是最大值和最小值,只需要做两遍单调队列即可
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define maxn 1000009
#define maxm
inline ll read()
{
ll x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=(x<<)+(x<<)+(ll)(ch-'');ch=getchar();}
return x*f;
}
int q[maxn],a[maxn];
int n,m,k,ans,tot,head,tail; void Ask_MIN()
{
head=,tail=;
for(int i=;i<=n;i++)
{
while(head<=tail&&i-q[head]+>m)
head++;
while(head<=tail&&a[q[tail]]>=a[i])
tail--;
q[++tail]=i;
if(i>=m)
printf("%d ",a[q[head]]);
}
puts("");
} void Ask_MAX()
{
head=,tail=;
for(int i=;i<=n;i++)
{
while(head<=tail&&i-q[head]+>m)
head++;
while(head<=tail&&a[q[tail]]<=a[i])
tail--;
q[++tail]=i;
if(i>=m)
printf("%d ",a[q[head]]);
}
puts("");
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();
for(int i=;i<=n;i++)
a[i]=read();
Ask_MIN();
Ask_MAX();
fclose(stdin);
fclose(stdout);
return ;
}
挤奶牛:https://www.luogu.org/problemnew/show/P3088
解题思路:此题题目需要维护左和右分别D区间内的最大值,因此可以正着和倒着分别做一次单调队列,然后打标记即可。
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define maxn 50009
#define maxm
inline ll read()
{
ll x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=(x<<)+(x<<)+(ll)(ch-'');ch=getchar();}
return x*f;
}
struct cow
{
int h,x;
}p[maxn];
int q[maxn];
bool fear[maxn];
int n,m,k,ans,tot,head,tail;
bool comp(cow a,cow b)
{
return a.x<b.x;
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();
for(int i=;i<=n;i++)
p[i].x=read(),p[i].h=read();
sort(p+,p++n,comp);
head=,tail=;
for(int i=;i<=n;i++)
{
while(head<=tail&&p[i].x-p[q[head]].x>m)
head++;
while(head<=tail&&p[i].h>=p[q[tail]].h)
tail--;
q[++tail]=i;
if(p[q[head]].h>=*p[i].h)
fear[i]=;
}
head=,tail=;
for(int i=n;i>=;i--)
{
while(head<=tail&&p[q[head]].x-p[i].x>m)
head++;
while(head<=tail&&p[q[tail]].h<=p[i].h)
tail--;
q[++tail]=i;
if(p[q[head]].h>=p[i].h*&&fear[i])
ans++;
}
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return ;
}
好消息,坏消息:https://www.luogu.org/problemnew/show/P2629
解题思路:先断环成链,便于操作,然后就变成求对于每一个合法的K,都要满足k到(n-k+1)中,任意一点的和都是非负的,用前缀和计算区间和,那么只需要满足sum[i]-sum[k-1]>=0(k<=i<=n+k-1),不需要判断每一个点,只需要转换一下变成判断最小的sum[i]减去最大的sum[k-1]是否大于等于0就行了,因为只要有一个点为负数就已经不合法了。
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define maxn 1000009
#define maxm
inline ll read()
{
ll x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=(x<<)+(x<<)+(ll)(ch-'');ch=getchar();}
return x*f;
}
int sum[maxn<<],q[maxn<<],a[maxn<<];
int n,m,k,ans,tot,head,tail;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();
for(int i=;i<=n;i++)
a[i]=read(),a[i+n]=a[i];
for(int i=;i<=*n;i++)
sum[i]=sum[i-]+a[i];
head=,tail=;
for(int i=;i<=n*-;i++)
{
while(head<=tail&&i-q[head]+>n)
head++;
while(head<=tail&&sum[i]<=sum[q[tail]])
tail--;
q[++tail]=i;
if(i>=n&&sum[q[head]]-sum[i-n]>=)
ans++;
}
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return ;
}
单调队列优化DP:
大部分单调队列优化的DP都和定长连续子区间的最值问题有关。
单调队列一般优化线性DP,形如dp[i]=max/min(dp[j])+val[i],且j<i,val[i]与dp[j]无关,此时优化的对象是dp[j]。
一般来说i,j是需要通过两层嵌套循环来实现枚举,但是因为dp[j]与val[i]无关,所以我们可以维护一下已经计算好了的dp[j],使其不需要用for循环来枚举。
例题引入:
最大连续和:https://loj.ac/problem/10176
给你一个长度为 n 的整数序列,要求从中找出一段连续的长度不超过 m 的子序列,使得这个序列的和最大。
例题解答:
首先考虑DP方程:用dp[i]表示以i为结尾的长度不超过m的最大子序列和。
转移为:dp[i]=max{sum[i]-sum[i-k],k=1.2....m}
=sum[i]-min{sum[i-k],k=1.2....m};
最后转变为对于所有的1<=k<=m,找出所有sum[i-k]的最小值。
考虑用单调队列来维护决策值sum[i-k]就行啦。
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define maxn 200009
#define maxm
inline ll read()
{
ll x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=(x<<)+(x<<)+(ll)(ch-'');ch=getchar();}
return x*f;
}
int sum[maxn],q[maxn];
int n,m,k,ans,tot,mx,head,tail;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();
for(int i=;i<=n;i++)
{
int x=read();
sum[i]=sum[i-]+x;
}
ans=-0xffffff;
head=,tail=;
for(int i=;i<=n;i++)
{
while(head<=tail&&i-q[head]>m)
head++;
ans=max(ans,sum[i]-sum[q[head]]);
while(head<=tail&&sum[q[tail]]>=sum[i])
tail--;
q[++tail]=i;
}
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return ;
}
习题报告:
切蛋糕:https://www.luogu.org/problemnew/show/P1714
解题思路:和例题一样。
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define maxn 500009
#define maxm
inline ll read()
{
ll x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=(x<<)+(x<<)+(ll)(ch-'');ch=getchar();}
return x*f;
}
int sum[maxn],q[maxn];
int n,m,k,ans,tot,mx,head,tail;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();
for(int i=;i<=n;i++)
{
int x=read();
sum[i]=sum[i-]+x;
}
head=,tail=;
for(int i=;i<=n;i++)
{
while(head<=tail&&i-q[head]>m)
head++;
ans=max(ans,sum[i]-sum[q[head]]);
while(head<=tail&&sum[q[tail]]>=sum[i])
tail--;
q[++tail]=i;
}
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return ;
}
琪露诺:https://www.luogu.org/problemnew/show/P1725
解题思路:用dp[i]表示到达点i时获得的最大冰冻指数,dp[i]=max{dp[i-j]}+a[i],l<=j<=r<=i.
然后对于求max{dp[i-j]}用单调队列来优化。
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define maxn 200009
#define maxm
inline ll read()
{
ll x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=(x<<)+(x<<)+(ll)(ch-'');ch=getchar();}
return x*f;
}
int q[maxn],a[maxn],dp[maxn];
int n,m,k,ans,tot,head,tail,l,r;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read(),l=read(),r=read();
for(int i=;i<=n;i++)
a[i]=read();
head=,tail=;
for(int i=l;i<=n;i++)
{
while(head<=tail&&i-q[head]>r)
head++;
while(head<=tail&&dp[q[tail]]<=dp[i-l])
tail--;
q[++tail]=i-l;
dp[i]=dp[q[head]]+a[i];
}
ans=;
for(int i=n-r+;i<=n;i++)
ans=max(ans,dp[i]);
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return ;
}
最后推荐一个博客:https://blog.csdn.net/hjf1201/article/details/78729320
里面单调队列优化DP的基本题型都有,比较齐全。
单调队列以及单调队列优化DP的更多相关文章
- 联赛模拟测试18 A. 施工 单调队列(栈)优化DP
题目描述 分析 对于 \(Subtask\ 1\),可以写一个 \(n^3\) 的 \(DP\),\(f[i][j]\) 代表第 \(i\) 个建筑高度为 \(j\) 时的最小花费,随便转移即可 时间 ...
- 单调队列优化DP,多重背包
单调队列优化DP:http://www.cnblogs.com/ka200812/archive/2012/07/11/2585950.html 单调队列优化多重背包:http://blog.csdn ...
- bzoj1855: [Scoi2010]股票交易--单调队列优化DP
单调队列优化DP的模板题 不难列出DP方程: 对于买入的情况 由于dp[i][j]=max{dp[i-w-1][k]+k*Ap[i]-j*Ap[i]} AP[i]*j是固定的,在队列中维护dp[i-w ...
- hdu3401:单调队列优化dp
第一个单调队列优化dp 写了半天,最后初始化搞错了还一直wa.. 题目大意: 炒股,总共 t 天,每天可以买入na[i]股,卖出nb[i]股,价钱分别为pa[i]和pb[i],最大同时拥有p股 且一次 ...
- Parade(单调队列优化dp)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2490 Parade Time Limit: 4000/2000 MS (Java/Others) ...
- BZOJ_3831_[Poi2014]Little Bird_单调队列优化DP
BZOJ_3831_[Poi2014]Little Bird_单调队列优化DP Description 有一排n棵树,第i棵树的高度是Di. MHY要从第一棵树到第n棵树去找他的妹子玩. 如果MHY在 ...
- 【单调队列优化dp】 分组
[单调队列优化dp] 分组 >>>>题目 [题目] 给定一行n个非负整数,现在你可以选择其中若干个数,但不能有连续k个数被选择.你的任务是使得选出的数字的和最大 [输入格式] ...
- [小明打联盟][斜率/单调队列 优化dp][背包]
链接:https://ac.nowcoder.com/acm/problem/14553来源:牛客网 题目描述 小明很喜欢打游戏,现在已知一个新英雄即将推出,他同样拥有四个技能,其中三个小技能的释放时 ...
- bzoj1233 单调队列优化dp
https://www.lydsy.com/JudgeOnline/problem.php?id=1233 数据结构优化dp的代码总是那么抽象 题意:奶牛们讨厌黑暗. 为了调整牛棚顶的电灯的亮度,Be ...
- POJ1821 单调队列//ST表 优化dp
http://poj.org/problem?id=1821 当我们在考虑内层循环j以及决策k的时候,我们可以把外层变量i看作定值,以此来优化dp状态转移方程. 题意 有n个工人准备铺m个连续的墙,每 ...
随机推荐
- VS2017+mysql5.7 连接数据库生成实体
参考:https://www.cnblogs.com/RushPasser/p/5438334.html 下载:https://share.weiyun.com/5rM4FrG mysql-for-v ...
- Kubeadm安装的K8S集群1年证书过期问题的解决思路
这个问题,很多使用使用kubeadm的用户都会遇到. 网上也有类似的帖子,从源代码编译这种思路, 在生产环境,有些不现实. 还是使用kubeadm的命令操作,比较自然一点. 当然,自行生成一套证书,也 ...
- 基于Redis的分布式锁到底安全吗
http://zhangtielei.com/posts/blog-redlock-reasoning.html
- [转] JavaScript 单例模式
定义 确保一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式使用的场景 比如线程池.全局缓存等.我们所熟知的浏览器的window对象就是一个单例,在JavaScript开发中,对于这种只需要 ...
- babelrc
.babelrc文件 // 简单版 { "presets": ["es2015", "stage-2"], // 使用 es2015 npm ...
- JMeter执行压测输出HTML图形化报表(二)
命令行模式将jtl转成测试图表 注意此方法只使用jmeter3.0以后版本 第一种:在测试过程中将jtl转成测试报告(在jmeter的bin目录下执行) jmeter -n -t baidu_requ ...
- zabbix邮箱报警设置&问题汇总
zabbix邮件报警部署! Zabbix监控服务端.客户端都已经部署完成,被监控主机已经添加,Zabiix监控运行正常,通过查看Zabbix监控服务器,可以了解服务器的运行状态是否正常,运维人员不会时 ...
- window下php5.5安装redis扩展
redis是现在比较流行的noSQL,主流大型网站都用的比较多,很多同学不知道怎么安装,这里介绍在windows下面安装以及扩展,提供学习使用,实际使用环境多在Linux下. 1.phpinfo(), ...
- BZOJ1856 [Scoi2010]字符串 数论
原文链接http://www.cnblogs.com/zhouzhendong/p/8084577.html 题目传送门 - BZOJ1856 题意概括 找出由n个1,m个0组成的字符串,且任意前几个 ...
- python josn包
Python josn包中的编码与解码方法 对于Python数据类型进行编码解码 json.dumps 对python的数据类型进行json格式编码 :(将dict转为json格式) eg: imp ...