【单调队列】求m区间最小值

链接

https://www.luogu.org/problemnew/show/P1440

大意

输入一个长度为 n 的序列,给定一个 m ,设 S i = m i n ( a k )     k = 1.. n 1 求出所有的 S i

思路

毫无疑问, S 1 必定等于0,因为1前面的最小值就是0,那么我们怎么考虑剩下的呢?

n 的范围决定了我们不能使用暴模,一种想法是我们每次新增一个就去比较一下是否大于最小值,然后放入。但是这种算法显然没有考虑到之前的因素,因为当 i > m 的时候,就要去掉第 i m 个,如果直接去掉的话可能导致整个数组发生变动,使得答案不满足最优性

我们可以通过单调队列+结构体去解决这个问题,具体怎么实现呢?我们看下面的思路

给定这个单调队列每个元素都有两个下标 i d n u ,分别表示当前元素出现的顺序以及其值。

对于每个新增进来的元素,我们给予插队,将其放入单调队列中

然后维护这个队列,当队首(也就是最大值)的编号大于等于 i m 时,也就是不满足前 m 个时,将其弹出队列。

这样,此队列的队首一定是前 m 个数的最小值

代码

#include<deque>
#include<cstdio>
using namespace std;
struct node{int id,nu;};
deque<node>q;
int n,m,f,a[2000001];char c;
int read()
{
    f=0;
    while(c=getchar(),c<=47||c>=58);f=(f<<3)+(f<<1)+c-48;
    while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
    return f;
}
void write(int x){if(x>9)write(x/10);putchar(x%10+48);return;}
int main()
{
    n=read();m=read();putchar(48);putchar(10);
    for(int i=1;i<n;putchar(10),i++)
    {
        a[i]=read();
        while(q.size()&&a[i]<q.back().nu) q.pop_back();//予以插队
        q.push_back(node{i,a[i]});//将其放入
        while(i-q.front().id>=m) q.pop_front();//维护
        write(q.front().nu);//输出
    }
}

代码2

其实代码1的基础上可以去掉结构体,因为 i 是可以表示 a [ i ] 的,所以就可以去掉 n u ,得到下面这个代码

#include<deque>
#include<cstdio>
using namespace std;
deque<int>q;
int n,m,f,a[2000001];char c;
int read()
{
    f=0;
    while(c=getchar(),c<=47||c>=58);f=(f<<3)+(f<<1)+c-48;
    while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
    return f;
}
void write(int x){if(x>9)write(x/10);putchar(x%10+48);return;}
int main()
{
    n=read();m=read();putchar(48);putchar(10);
    for(int i=1;i<n;putchar(10),i++)
    {
        a[i]=read();
        while(q.size()&&a[i]<a[q.back()]) q.pop_back();
        q.push_back(i);
        while(i-q.front()>=m) q.pop_front();
        write(a[q.front()]);
    }
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/80737937