单调队列即队列内元素单调递增或递减,删除数据可以在队头或者队尾,加入元素只能在队尾加入。
由于单调队列的队头一定是最小值,故查询为O(1);每个元素最多进队一次,出队一次,摊排分析下来仍然是O(1).
例1. 广告印刷
【问题描述】
最近,afy决定给TOJ印刷广告,广告牌是刷在城市的建筑物上的,城市里有紧靠着的N(N<=400000)个建筑。afy决定在上面找一块尽可能大的矩形放置广告牌。我们假设每个建筑物都有一个高度,从左到右给出每个建筑物的高度H1,H2…HN,0<Hi<=1,000,000,000,并且我们假设每个建筑物的宽度均为1。要求输出广告牌的最大面积。
【输入样例】
6
5 8 4 4 8 4
【输出样例】
24
【分析】
这道题目是要求每个建筑物向左向右能扩展到的最大宽度,即左右两边比它高的连续的宽度。显然暴力枚举O(n^2)的复杂度是不可行的。
考虑构造一个单调非递减队列,从左至右,依次加入到队列中,肯定会有元素出队列,设当前要插入的数为a,要出队列的数为b,必有b>=a,则b向右能到达的最远距离就是b-a。注意在求解时,让0先入队列,这样保证每个数据都会出队列。同理,左极限也可求出。
例2. POJ2823
【题意】
移动区间(长度固定)最值问题。
【分析】
这类思想在单调队列优化思想中尤其重要:区间长度为k,求区间内的最大值,考虑第i个数和第j个数,j-i<k,若a[i]<a[j],那么a[i]将毫无用处。直觉上理解,因为窗口的移动,a[i]要比a[j]先移出去,无论如何,区间的最大值都不可能是a[i]。
这样,考虑构造一个单调递增的队列,存放相应的序号,当a[队尾]>=要入队数据a[i],删除队尾元素;当队头<=i-k时,删除队头元素。
/**
单调队列:
加入找最小数,考虑顺序a,b(b在a的后面),若b<a,当b入队列后,a不可能称为最小值(a比b先出),删去。
每个元素出队列和入队列一次,时间复杂度为O(n)
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1100000;
int n,k;
int a[N];
int DanDiao_Que[N]; //单调递减队列(最大),单调递增队列(最小)
int head,tail;
//递增
void Min()
{
int i;
int head = 1;
int tail = 0;
for(i = 0; i < k-1; i++)
{
while(head<=tail && a[DanDiao_Que[tail]]>=a[i]) tail--;
tail++;
DanDiao_Que[tail] = i;
}
for(i = k-1; i < n; i++)
{
while(head<=tail && a[DanDiao_Que[tail]]>=a[i]) tail--;
tail++;
DanDiao_Que[tail] = i;
while(DanDiao_Que[head]< i-k+1) head++;
printf("%d",a[DanDiao_Que[head]]);
printf("%c",i==n-1?'\n':' ');
}
}
//递减
void Max()
{
int i;
int head = 1;
int tail = 0;
for(i = 0; i < k-1; i++)
{
while(head<=tail && a[DanDiao_Que[tail]]<=a[i]) tail--;
tail++;
DanDiao_Que[tail] = i;
}
for(i = k-1; i < n; i++)
{
while(head<=tail && a[DanDiao_Que[tail]]<=a[i]) tail--;
tail++;
DanDiao_Que[tail] = i;
while(DanDiao_Que[head]< i-k+1) head++;
printf("%d",a[DanDiao_Que[head]]);
printf("%c",i==n-1?'\n':' ');
}
}
int main()
{
scanf("%d %d",&n,&k);
for(int i = 0 ;i < n; i++)
scanf("%d",&a[i]);
Min();
Max();
return 0;
}
例3. HDU3415
【题意】
求长度不超过k的连续最大子段和。
【分析】
设dp[i]表示以i结尾的满足约束的最大子段和,sum[i]表示从0到第i个数的和,状态转移方程:dp[i]=sum[i]-min{sum[j]},i-k<=j<i,最后结果为max(dp[i]).
直接二重循环超时。设b[i]=i-k,b[i]随i的增长单调不降,可以考虑用单调队列优化。构造一个单调递减队列,维护操作类似例2.
/**
题意:有n(<=200000)个数排成一列,求长度不超过k(1<=k<=n)的连续的子串的和的最大值.
设dp[i]表示以a[i]为结尾的连续子串和的最大值,状态转移方程:
dp[i] = sum[i] - min{sum[j-1]}, max(0,i-k+1)<=j<i. dp[0]=0 最后结果为max{dp[i],1<=i<=n}
分析:
单纯二重循环O(n*k)肯定超时.
考虑求sum[j-1]的最小值 max(0,i-k)<=j<i,是否可以优化?
1.显然优先级队列可以适用,维护堆,时间复杂度优化为nlogk
2.考虑求解的单调性,若i<j且sum[i]>sum[j],则i可以被舍弃.只要维护一个递增的单调队列即可!
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int INF = 0x7fffffff;
const int N = 200005;
int n,k;
int a[N];
int sum[N];
int que[N];
int result,st,end;
void solve()
{
int head=1,tail=0;
result = -INF;
st = INF;
for(int i = 1; i <= n+k; i++)
{
while(head<=tail&&sum[i-1]<sum[que[tail]])
tail--;
while(head<=tail&&que[head]<i-k)
head++;
tail++;
que[tail] = i-1;
//output start
if(sum[i]-sum[que[head]]>result)
{
result = sum[i]-sum[que[head]];
st = que[head]+1;
end = i;
}
//output end
}
if(end>n)
end -= n;
}
int main()
{
int i;
int t;
scanf("%d",&t);
while(t--)
{
sum[0] = 0;
scanf("%d %d",&n,&k);
for(i = 1; i <= n; i++)
{
scanf("%d",&a[i]);
sum[i] = sum[i-1] + a[i];
}
for(i = n+1; i <= n+k; i++)
sum[i] = sum[i-1] + a[i-n];
solve();
printf("%d %d %d\n",result,st,end);
}
return 0;
}
例4. HDU3474
【题意】
由n(10^6)个数据组成的圆环,数据为1和-1,问从一个点开始顺时针或逆时针,能遍历完所有点,并且保证中间过程中sum>=0。
【分析】
首先O(n^2)可定不可行。假设从i点开始,这里仅考虑向左,必须保证sum(j,i)>=0,i-n<j<=i.设sum[i]为从开始到i的和,即保证sum[i]-sum[j]>=0,i-n<=j<i,即只要保证sum[i]-max{sum[j]}>=0即可。求移动区间(固定长度)最值问题,就可以联想到单调队列了。维护操作略。注意顺时针和逆时针计数时判重。
/**
题意:由n(10^6)个数据组成的圆环,数据为1和-1,问从一个点开始顺时针或逆时针,能遍历完所有点,并且保证中间过程中sum>=0。
分析:首先暴力O(n^2)是不可行的。
假设从i点开始,这里仅考虑向左,必须保证sum(j,i)>=0, i-n <j <= i.
设sum[i]表示从0到i点的和,即保证sum[i]-sum[j]>=0,即sum[i] - max(sum[j])>=0.
要求区间[i-n,i-1]最大值,维护单调递减队列即可。
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2100000;
int n;
char a[N/2];
int num[N];
int sum[N];
int que[N/2];
bool f[N/2],f2[N/2];
void solve(bool t)
{
int i;
int head=1,tail=0;
for(i = 1; i < n; i++)
{
while(head<=tail&&sum[que[tail]]<=sum[i]) tail--;
tail++;
que[tail] = i;
}
for(i = n; i <= 2*n; i++)
{
while(head<=tail&&que[head]<i-n) head++;
while(head<=tail&&sum[que[tail]]<=sum[i]) tail--;
tail++;
que[tail] = i;
if(sum[i]-sum[que[head]]>=0)
{
if(t)
f[i-n] = true;
else
f2[i-n] = true;
}
}
}
int main()
{
int i;
int t;
int cases = 0;
scanf("%d",&t);
while(t--)
{
memset(f,0,sizeof(f));
memset(f2,0,sizeof(f2));
sum[0] = 0;
scanf("%s",a+1);
n = strlen(a+1);
for(i = 1; i <= n; i++)
{
if(a[i]=='C')
num[i] = 1;
else
num[i] = -1;
}
for(i = 1; i <= n; i++)
num[i+n] = num[i];
for(i = 1; i <= n*2; i++)
sum[i] = sum[i-1] + num[i];
//1234512345
int result = 0;
solve(true);
//reverse
for(i = 1; i <= n; i++)
sum[i] = sum[i-1] + num[n+1-i];
for(i = n+1; i <=2*n; i++)
sum[i] = sum[i-1] + num[2*n+1-i];
//5432154321
solve(false);
result = 0;
for(i = 0; i < n; i++)
if(f[i]||f2[n-i])
result++;
printf("Case %d: %d\n",++cases,result);
}
return 0;
}
分享到:
相关推荐
### 单调队列及其应用 #### 一、单调队列基本概念 **定义:** 单调队列是一种特殊的数据结构,它维护了一个单调递增或递减的队列。这种队列允许我们在队尾添加元素,在队首删除元素,并保持队列中的元素始终处于...
这里我们将深入探讨这两种数据结构及其应用场景。 1. 单调队列(Monotonic Queue) 单调队列是一种特殊的队列,它保持队列内的元素按照一定的顺序(通常为非递减或非递增)。在操作过程中,新元素入队时,如果违反...
本章将深入探讨单调队列优化动态规划的概念、原理及其应用。 动态规划是一种通过将大问题分解为子问题来求解的方法。在动态规划中,我们通常会定义一个状态,然后根据已知的子问题状态来计算当前状态的最优解。然而...
具体内容涵盖了各种应用场景下的实现方法及代码实例,如栈用于括号匹配、单调栈求下一个更大元素、单调队列用于滑动窗口问题、差分用于快速更新区间、二分查找用于有序数据的高效检索等。 适合人群:参加 CSP-J 考试...
本文将深入探讨如何利用单调队列和其他基于单调性的方法来优化动态规划问题,并通过具体的题目实例来说明其应用。 #### 正文 ##### 一、什么是单调队列 单调队列,是一种特殊的队列结构,其内部元素具有单调递增...
### 动态规划中的单调性:深度解析与应用 在计算机科学领域,动态规划是一种用于求解具有重叠子问题和最优子...因此,对于动态规划问题的研究者而言,掌握单调性的概念及其应用技巧,是提升算法设计水平的关键所在。
总结来说,RMQ算法及其变种,如单调队列,是处理区间最值查询的有效工具,它们在处理大量数据时提供了显著的性能优势。在实际应用中,根据具体问题的需求选择合适的RMQ算法可以极大地优化程序的运行效率。通过深入...
+ [单调队列](#单调队列) + [哈希表](#哈希表) + [Splay](#splay) * [图论](#图论) + [图论](#图论-1) + [模型建立](#模型建立) + [网络流](#网络流) + [最短路](#最短路) + [欧拉路](#欧拉路) + [差分...
本文主要探讨了国家集训队2009论文集中关于背包问题的几种变化类型,包括完全背包问题、多次背包问题以及树形依赖背包问题等,还特别提到了单调队列优化技术在多重背包问题中的应用。 首先,我们需要了解0-1背包...
#### 一、队列及其应用 ##### 定义与特性 **队列**是一种特殊的线性表,遵循先进先出(FIFO, First-In-First-Out)的原则,即最先加入队列的元素最先被移除。队列主要在后端(队尾,rear)进行插入操作,在前端...
图论是研究图的性质及其在各种情况下的应用的数学分支。树的性质问题,如最小生成树和树的遍历算法是网络设计和优化中的重要工具。欧拉路径和哈密顿回路在解决交通规划和电路设计问题中有着实际应用。网络流问题在...
**单调队列**和**单调堆栈**都是用来维护数据结构中元素的单调性,即元素的增减顺序。这两种数据结构广泛应用于动态计算某些统计量,如滑动窗口内的最大值或最小值等问题。 - **单调队列**通常用于处理滑动窗口问题...
在计算机科学和算法设计中,贪心算法、二分搜索、前缀和与差分、单调队列/栈以及分治与倍增等策略和数据结构是解决复杂问题的有力工具。理解它们的原理、特点及其在实际问题中的应用场景,对于提高算法效率和解决...
余远铭:《最短路算法及其应用》 俞鑫:《棋盘中的棋盘——浅谈棋盘的分割思想》 周戈林:《浅谈类比思想》 周以苏:《论反汇编在时间常数优化中的应用》 朱晨光:《基本数据结构在信息学竞赛中的应用》 朱泽园:...
滑动窗口算法是一种在数据流或序列中...理解和掌握滑动窗口的概念、变种及其应用场景,对于提升算法设计能力具有重要的意义。在实际编程中,根据具体问题选择合适的数据结构和策略,可以大大提高代码的性能和可读性。
- 介绍单调栈和单调队列的设计思想及其应用场景。 #### 设计Twitter - 利用所学的数据结构设计一个简化版的Twitter功能。 #### 递归反转链表的一部分队列实现栈|栈实现队列 - 利用递归技巧实现链表反转的一部分,...
从给定的文件标题“算法动态规划总结(拓展篇)”和描述“算法动态规划拓展总结,内含详细解说和代码”中,我们可以提炼出一系列关于动态规划算法的知识点,这些知识点涵盖了动态规划的多种应用及其优化技术。...