关于单调队列
虽然单调队列不常考,都是经常和动态规划放一起,就让我们来了解一下。
所谓单调是指单调性,要么递增,要么递减。
这是个从队头到队尾的单调递增队列。
(ps:本文的队头用hh表示,队尾用tt表示,队头在左,队尾在右)
让我们看一下单调队列的实现。
for(int i=1;i<=n;i++)
{
while(hh<=tt && a[q[tt]]>=a[i])//单调递增队列
tt--;
q[++tt]=i;
}
for(int i=1;i<=n;i++)
{
while(hh<=tt && a[q[tt]]<=a[i])//单调递减队列
tt--;
q[++tt]=i;
}
-
单调递增队列
这里先给出一道单调递增队列的例题
P1440 求m区间内的最小值(洛谷上的)
题目描述
一个含有
输入格式
第一行两个整数,分别表示
第二行,
输出格式
之前
解法
对于这种求最小值的,我们一般采用单调递增队列(个人经验)。
我们不妨举个例子:
有这么几个数:
先进行进队操作,首先
随后,我们发现,
于是就要出队,
调换位置后,窗口滑动,现在在窗口里的数为
思路出来以后,就来看看代码。
#include<iostream>
#include<cstdio>
using namespace std;
const int N=2e6+200;
int n,m,a[N];
int q[N],hh,tt=-1;
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
if(hh<=tt && i-q[hh]>m)//如果队列不空,判断队头的下标是否超出范围
hh++;
printf("%d\n",a[q[hh]]);
while(hh<=tt && a[q[tt]]>=a[i])//单调递增队列
tt--;
q[++tt]=i;
}
return 0;
}
- 单调递减队列
基本思路同上,不再熬述。
习题1:P2032 扫描
习题2:P1886 滑动窗口 /【模板】单调队列
- 堆与单调队列的应用
例题:P1090 合并果子 / [USACO06NOV] Fence Repair G
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为
例如有
输入格式
共两行。
第一行是一个整数
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于
解法
对于这道题,我们很容易地想到,要使体力值最小,我们每次合并都得选最小的两个。
于是我们就会想到用 sort 排序。
时间复杂度就是
所以要用二叉堆来排序。
由于优先队列默认是大根堆,只能取最大值,所以我这里还涉及到重载运算符(让原来大的变“小”,小的变“大”)。
重载运算符的实现:
- 首先要载入库文件
#include<queue>
- 核心代码
struct node{
int t;
};
bool operator <(const node &x,const node &y)
{
return x.t>y.t;
/*重载小于号 ,优先队列默认是大根堆,只能取到最大值,
现在想取到最小值,所以需要把优先队列改为小根堆,需要重载小于号*/
}
那么实现小根堆以后,就可以用单调递增队列来做这道题
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
struct node{
int t;
};
priority_queue <node> q;
bool operator <(const node &x,const node &y)
{
return x.t>y.t;
/*重载小于号 ,优先队列默认是大根堆,只能取到最大值,
现在想取到最小值,所以需要把优先队列改为小根堆,需要重载小于号*/
}
node a[10100];
int main()
{
int n,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].t);
q.push(a[i]);
}
if(n==1)//特殊情况
{
printf("%d",a[1].t);
return 0;
}
while(true)//合并果子ing
{
node x,y;
x=q.top();
q.pop();
y=q.top();
q.pop();
ans=ans+x.t+y.t;//统计体力值
if(q.empty())//如果队列为空
{
break;//合并完了,走人
}
else
{
node z;
z.t=x.t+y.t;
q.push(z);//得到这次合并果子的体力值
}
}
printf("%d",ans);
return 0;
}
习题:P2168 [NOI2015]荷马史诗
- 单调队列优化DP
例题:298.围栏
题目描述
有
第
不同工匠的
请问如何安排能使工匠们获得的总报酬最多。
输入格式
第一行包含两个整数
接下来M行,每行包含三个整数
输出格式
输出一个整数,表示结果。