Tip:还有很多更有深度的题目,这里不再给出,只给了几道基本的题目(本来想继续更的,但是现在做的题目不是这一块内容,以后有空可能会继续补上)

单调队列——看起来就是很高级的玩意儿,显然是个队列,而且其中的元素还具有单调性

当然,它不只只是一个简单的队列,还是一个双端队列,即队首队尾都可以弹出元素,当然可以用C++自带的STL<deque>实现,当然这篇博客里不建议使用这种写法,因为不开O2的话就会有一个大弊端——

单调队列裸题:滑动窗口

  线性的求一个区间内的最值,我们先来找个规律,比如这组数据:

  8 3
  1 3 -1 -3 5 3 6 7

(以求最大值为例子)

  我们发现前两个数中 1  3   ,3的优先级明显大于1,原因?3比1大,而且3还在1的右边(这样在后面的更新中3还能起到作用,而1显然已经没有作用了,那么我们就可以把1从队列里面弹出去了!)当然,这个操作在3丢到队列里面的时候就可以进行了,同时在这个操作之前,我们还要把前面的元素和现在位置差距大于k的元素弹掉

  这样我们便可以保证队列中的元素是单调递增的,那么我们每次在n中取出k个元素的时候,只要把当前队列中的第k个元素放到输出列表中就好了!

  当然最小值也是一样的,维护一个单调递减的序列,在这个过程中,每次输出其中最小数

代码如下:(先求最小值,后求最大值)

 #include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
int ans=,f=; char chr=getchar();
while(!isdigit(chr)){if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+chr-;chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) x=-x,putchar('-');
if(x>) write(x/);
putchar(x%+);
}
int q[],h,t,n,a[],k;
int main(){
n=read();k=read();
for(register int i=;i<=n;++i) a[i]=read();
h=,t=;
for(register int i=;i<=n;++i){//Min
while(h<=t&&q[h]+k<=i) ++h;
while(h<=t&&a[i]<=a[q[t]]) --t;
q[++t]=i;
if(i>=k) write(a[q[h]]),putchar(' ');
}puts("");
h=,t=;
for(register int i=;i<=n;++i){//Max
while(h<=t&&q[h]+k<=i) ++h;
while(h<=t&&a[i]>=a[q[t]]) --t;
q[++t]=i;
if(i>=k) write(a[q[h]]),putchar(' ');
}
return ;
}

【时间复杂度分析】

   显然外循环的复杂度为n,关键在于其中的while循环,分析一下可以知道,每一个元素在其中只会进入队列一次,出队列一次,所以总的时间复杂度为O(n),而且常数也是十分优秀的

     当然像这种求区间最值的问题也有一种简单粗暴的方法:线段树

代码如下:(这里代码就折叠掉了,有需求的读者可以自己阅读,就是求区间的最大值和最小值,连修改都不用,可以说是线段树的模板了)

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#define ll long long
#define lson i << 1,l,m
#define rson i << 1| 1,m + 1,r
#define MAXN (int)1e6 + 5
using namespace std; inline ll read(){
char chr=getchar();
ll f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=ans*;ans+=chr-'';chr=getchar();}
return ans*f; } void write(ll x){
if(x<){
putchar('-');
x=-x;
}
if(x<)
putchar(x+'');
else
write(x/),putchar(x%+);
} struct P{
ll l,r;
ll max,add,min;
ll mid(){
return l + r >> ;
}
}t[MAXN << ];
ll a[MAXN << ]; void build(ll i,ll l,ll r){
t[i].l = l;t[i].r = r;
if(l == r){
t[i].min = a[l];
t[i].max = a[l];
return;
}
ll m = t[i].mid();
build(lson);
build(rson);
t[i].max =max( t[i << ].max , t[i << | ].max );
t[i].min =min( t[i << ].min , t[i << | ].min );
} ll qmin(ll i,ll l,ll r){
if(l <= t[i].l && t[i].r <= r) return t[i].min;
ll pp = 0x3f3f3f3f,qq = 0x3f3f3f3f;
ll m = t[i].mid();
if(l <= m) pp = qmin(i << ,l,r);
if(r > m) qq = qmin(i << | ,l,r);
return min(qq , pp);
} ll qmax(ll i,ll l,ll r){
if(l <= t[i].l && t[i].r <= r) return t[i].max;
ll pp = -0x3f3f3f3f,qq = -0x3f3f3f3f;
ll m = t[i].mid();
if(l <= m) pp = qmax(i << ,l,r);
if(r > m) qq = qmax(i << | ,l,r);
return max(qq , pp);
} ll n,m;
int main(){
n = read() ;
m = read() ;
for(ll i = ;i <= n;i ++)
a[i] = read();
build(,,n);
for(int i = ;i + m - <= n;++i){
printf("%lld",qmin(,i,i+m-));
putchar(' ');
}
puts("");
for(int i = ;i + m - <= n;++i){
printf("%lld",qmax(,i,i+m-));
putchar(' ');
}
return ;
}

      

【NO.1】

  【NOIP提高组初赛程序填空】烽火传递

题目描述

烽火台是重要的军事防御设施,一般建在交通要道或险要处。一旦有军情发生,则白天用浓烟,晚上有火光传递军情。

在某两个城市之间有 nn 座烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确传递,在连续 mm 个烽火台中至少要有一个发出信号。现在输入 n,mn,m 和每个烽火台的代价,请计算总共最少的代价在两城市之间来准确传递情报。

输入格式

第一行是 n,mn,m,表示 nn 个烽火台和连续烽火台数 mm;

第二行 nn 个整数表示每个烽火台的代价 a_iai​。

输出格式

输出仅一个整数,表示最小代价。

样例

样例输入

5 3
1 2 5 6 2

样例输出

4

样例说明

在第 2,52,5 号烽火台上发信号。

数据范围与提示

n,m正整数且小于等于2×10^5

【分析】

  显然是一道DP题

  不妨令f[i]表示取第i个点时的最小值

  那么有方程:

f[i]=min{f[k]}+a[i]

其中k∈[i-m,i-1]

  但显然这样是O(n^2)的复杂度,对于十万级的n,m显然不够优,如果我们可以在很短的时间内求出f[k](k∈[i-m,i-1])的最小值就好了,并且随着i的增大,它每次还能更新入新的数据!

  区间最小?单点更新?不就是线段树吗!O(nlogn)的算法出炉(还是一样,先把代码折叠起来,有需求的读者可以自己点开看):(当然,如果读者不会线段树,可以跳过这一部分的代码直接阅读后面)

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
inline int read(){
char chr=getchar(); int f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) putchar('-'),x=-x;
if(x>) write(x/);
putchar(x%+'');
}
int n,m,a[<<];
int minn[<<];
void updata(int i,int l,int r,int pos,int x){
if(l==r){minn[i]=x;return;}
int mid=l+r>>;
if(pos<=mid) updata(i<<,l,mid,pos,x);
else updata(i<<|,mid+,r,pos,x);
minn[i]=min(minn[i<<],minn[i<<|]);
}
int query(int i,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return minn[i];}
int mid=l+r>>,x=0x3f3f3f3f,y=0x3f3f3f3f;
if(ql<=mid) x=query(i<<,l,mid,ql,qr) ;
if(qr>mid) y=query(i<<|,mid+,r,ql,qr);
return min(x,y);
}
int f[<<];
signed main(){
freopen("ttt.in","r",stdin);
n=read();m=read();
for(int i=;i<=n;i++)a[i]=read();
for(int i=;i<m;i++) updata(,,n,i,a[i]);
for(int i=m;i<=n;i++){
f[i]=query(,,n,i-m,i-)+a[i];
updata(,,n,i,f[i]);
}int ans=0x7fffffff;
for(int i=n-m+;i<=n;i++) ans=min(f[i],ans);
write(ans);
return ;
}

  对于本题,已经可以在要求的时间内求出答案了,但是显然这样的办法有点异常暴力,而且还要照顾一下不会线段树的童鞋是吧,于是切入正题,如何用单调队列做这题!

  对于每一次状态的转移,我们只需要维护f[]数组的最值即可,那么显然我们可以以f[]创建一个单调队列,维护f[]的最小值,每次更新它的最新元素,且每次更新即取队首元素即可

  当然,上面的while循环可以换成在C++更加里面更加灵活的for循环,本质上还是一个求最值的问题,不过在这之前我们要能从中推出转移方程,关键是要从递推式中看出单调性,这也是我们用单调队列解题的前提

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
inline int read(){
char chr=getchar(); int f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) putchar('-'),x=-x;
if(x>) write(x/);
putchar(x%+'');
}
const int N=1e6+;
int n,m,l,r,a[N],f[N],q[N<<];
signed main(){
n=read();m=read();
for(int i=;i<=n;i++) a[i]=read();
l=r=;
for(int i=;i<=n;i++){
for(;l<r&&i-q[l]>m;l++);
f[i]=f[q[l]]+a[i];
for(;l<r&&f[q[r]]>f[i];r--);
q[++r]=i;
}int ans=0x7fffffff;
for(int i=n-m+;i<=n;i++) ans=min(ans,f[i]);//这里有一个小细节,最后一次选择可以从最后m个元素中选择,原因很简单,只要保证后面m个元素中有一个取就够了
cout<<ans;
return ;
}

【NO.2】

  【Tyvj1305】最大子序和

【问题描述】

输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。
例如 1,-3,5,1,-2,3
当m=4时,S=5+1-2+3=7;
当m=2或m=3时,S=5+1=6。

【输入格式】

第一行两个数n,m;
第二行有n个数,要求在n个数找到最大子序和。

【输出格式】

一个数,数出他们的最大子序和。

【输入样例】

6 4
1 -3 5 1 -2 3

【输出样例】

7

【数据范围】

n,m≤300000;数列元素的绝对值≤1000。

【题目来源】

Tyvj1305

【问题分析】  

  首先,要求连续我们可以把原序列转化成前缀和进行求解

  因为前缀和的性质有sum[l~r]=sum[r]-sum[l-1],对于每一个r,我们只要求出前面m个数中最小的sum[l]即可保证sum[l~r]最大

  以sum[]建立单调队列即可

  

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
char chr=getchar(); int f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) putchar('-'),x=-x;
if(x>) write(x/);
putchar(x%+'');
}
const int M=;
int n,m;
int q[M],a[M],h,t,f[M];
int sum[M];
int main(){
n=read(),m=read();
for(int i=;i<=n;i++) a[i]=read(),sum[i]=sum[i-]+a[i];
int l=,r=;int ans=-0x7fffffff;
for(int i=;i<=n;i++){
for(;l<r&&i-q[l]>m;l++);
ans=max(ans,sum[i]-sum[q[l]]);
for(;l<r&&sum[q[r]]>=sum[i];r--);
q[++r]=i;
}
cout<<ans;
return ;
}

【NO.3】

  【Hdu3530Subsequence

【问题描述】

  给定一个包含n个整数序列,求满足条件的最长区间的长度:该区间内的最大数和最小数的差不小于m,且不大于k。

【输入格式】

输入包含多组测试数据:对于每组测试数据:
第一行,包含三个整数n,m和k;
第二行,包含n个整数的序列。

【输出格式】

对于每组测试数据,输出满足条件的最长区间的长度。

【输入样例】

5 0 0
1 1 1 1 1
5 0 3
1 2 3 4 5

【输出样例】

5
4

【数据范围】

1≤n≤100000;
0≤m,k≤100000;
0≤ai≤100000

【题目来源】

Hdu3530

【题目分析】

  其实也是模板题了,只不过要求同时维护最大值最小值而已

  这里不再提最大值最小值的更新了,而是给出这道题里新的东西:要使最大值减最小值在区间[l,r]中的话,一旦当前的序列最大值减最小值不再该区间内了,便要继续弹出元素

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
char chr=getchar(); int f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) putchar('-'),x=-x;
if(x>) write(x/);
putchar(x%+'');
}
const int M=;
int q1[M],q2[M],a[M],n,m,k,t1,t2,tt1,tt2,ttt1,ttt2,ans;
int main(){
while(~scanf("%d%d%d",&n,&m,&k))
{
for(int i=;i<=n;i++) a[i]=read();
memset(q1,,sizeof(q1));
memset(q2,,sizeof(q2));
t1=;t2=;ttt1=;ttt2=;ans=;tt1=;tt2=;
for(int i=;i<=n;i++){
while(t1<ttt1&&a[q1[ttt1-]]<=a[i])ttt1--; //maxn
q1[ttt1++]=i;
while(t2<ttt2&&a[q2[ttt2-]]>=a[i])ttt2--; //minn
q2[ttt2++]=i;
while(a[q1[t1]]-a[q2[t2]]>k)
if(q1[t1]<q2[t2]) tt1=q1[t1++];
else tt2=q2[t2++];
if(a[q1[t1]]-a[q2[t2]]>=m)
ans=max(ans,i-max(tt1,tt2));
}
write(ans),puts("");
}
return ;
}

最新文章

  1. [翻译]AKKA笔记 - ACTOR MESSAGING - REQUEST AND RESPONSE -3
  2. Unity3D 为什么保存Transform等引用效率会更高
  3. ASP.NET MVC显示HTML字符串
  4. mysql基础一
  5. CTSC2015 酱油记
  6. C++除法取整
  7. Design Mode 之 创建模式
  8. js格式化日期,获取当月的第一天,与最后一天.
  9. Delphi Unable to invoke Code Completion due to errors in source code
  10. [Hive - LanguageManual] Sampling
  11. 【转】非常详细的docker学习笔记
  12. Eclipse+maven发布ee项目jar包未发布
  13. Android WiFiDirect 学习(二)——Service Discovery
  14. LINUX编程学习笔记(十四) 创建进程与 父子进程内存空间
  15. for i in xrange(0,5)使用过程中遇到的问题
  16. Postman编程
  17. Libgdx 1.6.1发布,跨平台游戏开发框架
  18. 记一次504 Gateway Time-out
  19. WPF仿网易云音乐系列(序)
  20. 消息中间件系列五:RabbitMQ的使用场景(异步处理、应用解耦)

热门文章

  1. Eclipse安装和使用TFS
  2. Async/await语法糖实现(Generator)
  3. colgroup 整行变色
  4. 阅读《JavaScript设计模式》第三章心得
  5. 5.terms搜索多个值以及多值搜索结果优化
  6. Yii2开发技巧 使用类似闭包的方式封装事务
  7. LINUX应用开发(面试)
  8. C# 派生类的XmlSerializer序列化XML
  9. 剖析Spark-Shell
  10. sicily 10330. Cutting Sausages