• 【学习笔记】单调队列 & 单调栈


    单调队列和单调栈都是维护单调性的线性数据结构。

    如果了解过 RMQ 的同学可能知道,大部分 RMQ 数据结构的区间查询复杂度都是 (O(log n)) 级别的。但是单调队列和单调栈却能在 (O(1)) 的时间内完成类似操作。

    想要达成这一点,要利用队列和栈的结构做文章。

    达成单调性

    单调队列和单调栈的单调性实际上是结构内的单调性,而且这种单调性是双重的。

    第一重单调性是 值的单调性。这个很好理解,就是结构内的元素的值按照一定的顺序排列。

    第二重单调性则是 过期时间的单调性,这就是单调队列和单调栈的关键所在。

    每一个元素在插入结构时,都「预定」了一个过期时间。这个时间并不会改变,而且与插入时间正相关。

    补充:「正相关」的意思就是「一个越……,另一个也越……」,或者说如果 (x_i<x_j),那么 (y_i<y_j)

    为了维护这双重单调性,结构内必然要对元素进行删减。


    不论是单调队列还是单调栈,都使用正面开口进行插入和元素调整。不同之处仅在于单调队列用后面的开口进行删除,而单调栈使用正面。接下来就使用单调队列进行演示。

    首先,队列内原本保持双重单调性,但是新的元素插入,就会导致单调性被破坏:

    aeALzn.png

    下一步,就是将在队列前面的,不如这个元素优的元素弹出,然后再插入:

    aeEz6I.png

    如果一个人比你小还比你强,那么你就永远超不过 Ta 了。 —— chen_zhe

    常用模型

    下面介绍一些常用的单调队列/单调栈应用。

    滑动窗口

    顾名思义,这就是一个框定大小进行的 RMQ。

    使用单调队列可以很容易解决。

    限定区间大小的最大/最小区间和

    这也是单调队列的经典应用。

    求出前缀和以后,对于每一个区间末尾,寻找一个区间开头就是 RMQ。

    单调栈内二分

    这是一个比较冷门的技巧。常用于限定末尾,没有限定开头,且末尾需要插入的区间查询问题。

    具体请看 【题解】最大数(咕咕咕)。

    例题

    滑动窗口

    单调队列模板题。

    #include <cstdio>
    #include <cctype>
    #include <queue>
    using namespace std;
    
    const int max_n = 1000000;
    
    struct item
    {
    	int ind, val;
    	
    	item(int _i = 0, int _v = 0) : ind(_i), val(_v) { }
    };
    
    deque<item> mx, mn;
    
    int res1[max_n], res2[max_n];
    
    inline int read()
    {
    	int ch = getchar(), n = 0, t = 1;
    	while (isspace(ch)) { ch = getchar(); }
    	if (ch == '-') { t = -1, ch = getchar(); }
    	while (isdigit(ch)) { n = n * 10 + ch - '0', ch = getchar(); }
    	return n * t;
    }
    
    int main()
    {
    	mx.clear();
    	mn.clear();
    	
    	int n = read(), k = read(), tmp;
    	
    	for (int i = 0; i < n; i++)
    	{
    		tmp = read();
    		
    		while (!mx.empty())
    		{
    			if (mx.front().ind <= i - k)
    				mx.pop_front();
    			else
    				break;
    		}
    		while (!mn.empty())
    		{
    			if (mn.front().ind <= i - k)
    				mn.pop_front();
    			else
    				break;
    		}
    		
    		while (!mx.empty())
    		{
    			if (mx.back().val <= tmp)
    				mx.pop_back();
    			else
    				break;
    		}
    		mx.emplace_back(i, tmp);
    		while (!mn.empty())
    		{
    			if (mn.back().val >= tmp)
    				mn.pop_back();
    			else
    				break;
    		}
    		mn.emplace_back(i, tmp);
    		
    		if (i >= k - 1)
    		{
    			res1[i-k+1] = mx.front().val;
    			res2[i-k+1] = mn.front().val;
    		}
    	}
    	
    	for (int i = k; i <= n; i++)
    		printf("%d ", res2[i-k]);
    	putchar('
    ');
    	for (int i = k; i <= n; i++)
    		printf("%d ", res1[i-k]);
    	putchar('
    ');
    	
    	return 0;
    }
    

    琪露诺

    这就是不折不扣的用单调队列优化 DP 的模板题。

    定义 (f_i) 为琪露诺跳到第 (i) 格的最大值。一步从 (i) 跳到 ([i+L,i+R]),相当于 (f_{i^{prime}})(f_{i^{prime}-R})(f_{i^{prime}-L}) 之间转移。

    因为 (v_i) 是固定的,所以相当于一个 RMQ,使用单调队列解决。最后再求一个最大值就好了。

    切蛋糕

    这是一个相当经典的模型——限定区间大小的最大/最小区间和。这也是用单调队列解决的。

    我们先求出前缀和,然后遍历。对于每一个位置 (i),相当于求 (S_{i-k} (1le kle m)) 的极值,其中 (m) 是大小限制。

    然后,用 (S_i-S_u) 更新答案就可以了,(S_u) 是极值。注意这里的最大/最小要与区间和要求的最小/最大相反(毕竟要减掉嘛)。

    Largest Rectangle in a Histogram

    这是另一个经典的模型——最大折线下正方形。(咕咕咕)

  • 相关阅读:
    LC 面试题56
    AspNet 执行存储过程带参数 and Entity Framework 之存储过程篇
    SQL中Charindex和Oracle中对应的函数Instr
    Spring MVC遭遇checkbox的问题解决方案
    Spring MVC 的 multipartResolver 不能同iWebOffice2006 共同使用
    Spring CommonsMultipartResolver 上传文件
    解决自定义文件上传处理与Spring MultipartResolver的冲突问题
    GooFlow
    js判断undefined类型
    mybatis generator tools配置文件解析
  • 原文地址:https://www.cnblogs.com/5ab-juruo/p/note-monotonous_stack_queue.html
Copyright © 2020-2023  润新知