辣鸡千灯qwq
单调队列是一种队列(逃
单调队列可以维护【最值】达到O(n)的效果(连dp都要O(n2)
可想而知单调队列是【单调】的,单调队列保持从小到大或者从大到小。
{1,2,3,4,5,6,7}从小到大的单调队列(维护最小值
{7,6,5,4,3,2,1}从大到小的单调队列(维护最大值
这样的队列有一个特点:他们的【最值】都在队头。
我们所说的【维护】单调队列就是要保持最值持续在队头。
那么也许你会说可以不用【单调】啊?
但是【单调】有许多好处。(不用单调你能达到O(n)效果我倒立给你看
就{1,2,3,4}来看(维护最小值。得到4条【信息】
a.1是目前的【最值】
b.2是比3,4【更优】的值
c.3是比4【更优】的值
d.4是目前【最差】的值
这个时候【最小值】当然是1.
但是当我们发现1不合法的时候(题目需要,要把1从队头出队,这个时候由于【单调性】2是仅此于1的最优值,2就成为队头。
{2,3,4}
这个时候如果在队尾加入1
{2,3,4,1}
这样破坏了单调性。
又获得了2条信息
a.2在1未进队时的【最优】值
b.1进队后比前面的好
我们会选择把4给抹杀掉
{2,3,1}
3还是没1好
{2,1}
2没1好。
{1}
队头保持最值。
这样的做法是O(n)的
但是有一个致命缺陷。
如果1比2提前不合法,也许2才是那一个时刻的最值。
因此单调队列的【不合法性】必须是按照一定顺序的
例题
洛谷P1440 求m区间内的最小值
首先我们发现这是【依次】求值,因此【不合法性】是按照从前到后的顺序(超过m就是不合法的。
这个时候单调队列就出场了。
我们【维护】一个单调队列,使队头一直保持最小值。
首先开一个for
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
cout<<"0";
读入,没啥好说的,我们发现最开始肯定要输出一个0。。。(逃
然后就是单调队列的维护。
for(int i=1;i<=n-1;++i)
{
while(q.size()>0&&q.front().key<i-m+1) q.pop_front();
//首先将不合法的出队,但同时保证队列不能比空还少。
while(q.size()>0&&q.back().pos>a[i]) q.pop_back();
q.push(node{a[i],i});
//如果在a[i]进队后发现先前面的没他好,为了保持单调性,把前面的全部出队
//不用担心队头不是最优的
//我们知道这个队列是依次进队的,越后面进队的合法时间越长
//那么队中情况:{合法时间最少的,次少的,次次少的,合法时间最长的}
//如果这个时候:{值大,值大,值大,值小}
//这样合法时间又长,又是很小的值。绝对是最优的!前面没有一个比的上
cout<<q.front()<<endl;
//输出该时刻最优值
}
代码上有注释,请仔细看qwq
贴上AC代码。
#include<bits/stdc++.h>
#define maxn 10000010
using namespace std;
int n,m,a[maxn];
struct node
{
int pos,key;
node(){}
node(int a,int b): pos(a),key(b){}
//为了能使用node{a,b};
};
deque <node> q;
bool operator <(node a,node b)
{
return a.pos>b.pos;
}
//重载,把小的推到前面
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
cout<<"0"<<endl;
for(int i=1;i<=n-1;++i)
{
while(q.size()>0&&q.front().key<i-m+1) q.pop_front();
while(q.size()>0&&q.back().pos>a[i]) q.pop_back();
q.push_back(node{a[i],i});
printf("%d\n",q.front().pos);
}
return 0;
}
单调队列是一种数据结构,用于在O(n)的时间复杂度内维护序列的最值。它既可以维护序列的最小值也可以维护最大值,且始终保持最值在队头。当序列中元素的不合法性按顺序出现时,单调队列能有效处理这种情况。以洛谷P1440题为例,该题要求求解m个区间内的最小值,通过单调队列可以高效地实现这一功能。在代码实现中,需要注意队列的维护,确保不合法元素按照顺序出队,以保持队头的最值属性。
1005

被折叠的 条评论
为什么被折叠?



