这俩货好像有那么点关系,就把它们一起讲了。
首先讲一讲单调栈,这个玩意儿似乎没有什么板子,但是这一类题目看懂了其实都比较板。还是日常先来看一道题:音乐会的等待
题目描述
N个人正在排队进入一个音乐会。人们等得很无聊,于是他们开始转来转去,想在队伍里寻找自己的熟人。队列中任意两个人 A A 和,如果他们是相邻或他们之间没有人比 A A 或高,那么他们是可以互相看得见的。
写一个程序计算出有多少对人可以互相看见。
输入输出格式
输入格式:
输入的第一行包含一个整数 N(1≤N≤500000) N ( 1 ≤ N ≤ 500 000 ) , 表示队伍中共有N个人。
接下来的N行中,每行包含一个整数,表示人的高度,以毫微米(等于 10−9 10 − 9 次方米)为单位,每个人的调度都小于 231 2 31 毫微米。这些高度分别表示队伍中人的身高。
输出格式:
输出仅有一行,包含一个数 S S ,表示队伍中共有对人可以互相看见。
输入输出样例
输入样例#1
7
2
4
1
2
2
5
1
输出样例#1
10
不得不说,这道题有一点坑,难点不在写单调队列上,而在特判情况上。
不过先说一下单调栈是什么。
我们要维护一个单调上升的栈,比如数据为:1,4,6,3,5。把这些元素一次加入栈内,并保持栈内单调,做法就是把不单调的元素弹出。模拟一下:
1
1,4
1,4,6
1,3
1,3,5
很明显,在3入栈时,我们把栈顶大于3的全部弹出,以此保证栈内单调。
单调栈有什么用?
如果我们维护的是一个单增序列,意思就是若 i<j i < j 且 ai>aj a i > a j ,那么 ai a i 对后面的答案没有影响(贡献)。
最简单的一个应用就是:求每一条红线向左在不碰到黑线的情况下向左延伸的最远距离。
可以发现,在蓝色箭头的右侧,绿色圈起来的黑线就挡不到红线延伸了(这两根线其实已经没有了任何意义)。而这个结论在单调栈中就是用蓝色箭头把这两根线弹出。也就是用小的高度把大的高度弹出。而且在弹完之后,剩下的第一条线(也就是栈顶)刚好是蓝色能到达的最远距离。
我们再回过头来看音乐会的等待这一道题。有了单调栈的思想,如果两个人中间有一个比其中一个人高,那么两个人不能互相看见。那我们顺势可以推出:若 i<j i < j 且 ai<aj a i < a j 那么对于任意的 x>j x > j 。 x x 和都不能相互看到。这就变成了单调栈,我们维护一个单减序列。而且我们发现,如果一个人把另一个人从栈中弹出,那么它们两个一定能互相看见。如果弹完之后栈中还有元素,这个人还能看到栈顶的人。(想想为什么)
这道题需要特别注意的就是相等的情况,如果弹完后栈顶元素和当前元素相等,当前元素不仅仅能看见栈顶。它能看到栈中所有与它相等的元素再加第一个比它大的元素。
然后附上代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;
struct lxy{
long long num,x;
};
int n;
long long a[500005];
long long ret;
stack <lxy> d;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
{
while(!d.empty()&&d.top().x<a[i])//弹栈
{
ret+=d.top().num;
d.pop();
}
if(!d.empty()&&d.top().x==a[i])//如果相等特判
{
lxy p=d.top();
ret+=p.num;
p.num++;
d.pop();
if(!d.empty()) ret++;
d.push(p);
continue;
}
else if(!d.empty()) ret++;//栈中不空,还能多看一个
lxy p;p.x=a[i],p.num=1;
d.push(p);
}
printf("%lld",ret);
}
好,我们单调栈就此完结,下面看一个单调队列的板子: m m 区间内的最小值
题目描述
一个含有项的数列 (n<=2000000) ( n <= 2000000 ) ,求出每一项前的 m m 个数到它这个区间内的最小值。若前面的数不足项则从第1个数开始,若前面没有数则输出0。
输入格式:
第一行两个数
n,m
n
,
m
。
第二行,
n
n
个正整数,为所给定的数列。
输出格式:
行,第 i i 行的一个数,为所求序列中第 i i 个数前个数的最小值。
输入样例#1
6 2
7 8 1 4 3 2
输出样例#1
0
7
7
1
1
3
个人认为单调队列是单调栈的升级版。思考一个问题:若
i<j
i
<
j
且
ai>aj
a
i
>
a
j
,那么区间右端点大于
j
j
的区间最小值都不会是。是不是很像单调栈?但是题目有一个限制,就是区间长度不超过
m
m
,所以我们栈底也要弹出。可以想到使用双端队列来实现。
实现其实也很简单了,入队尾,把队尾大于当前元素的弹出。若队首与队尾元素距离差超过,弹出队首。(这里封装结构体,存位置和值来实现)。队首就是当前区间的最小值。
再附上代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct lxy{
int x,id;
};
int n,k,l;
deque <lxy> d;
long long a[2000005];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
printf("0\n");
lxy p;p.x=a[1],p.id=1;
d.push_back(p);
for(int i=2;i<=n;i++)
{
printf("%d\n",d.front().x);
if(i-d.front().id>=k) d.pop_front();
while(!d.empty()&&d.back().x>a[i]) d.pop_back();
p.x=a[i],p.id=i;
d.push_back(p);
}
}
单调队列一般用于优化取最值的dp,有兴趣可以看看这一篇:修剪草坪
假装是数据结构优化dp,233。