单调栈

概述

栈中元素满足单调性的线性数据结构,单调栈一般维护的是一个数前/后第一个大于/小于他的数。

  1. 单调栈解决的主要问题是什么呢?

​ 就跟单调队列差不多。单调队列主要处理的是一个区间内的最大/小值,而单调栈处理的是寻找以某个值为最小/大值的最大区间。相比较,实际上单调栈用的虽然少一些,但是比单调队列更加灵活多变。

  1. 为什么单调栈是正确的呢?

​ 对于这道题来说,我们定义一个元素失效,当且仅当这个元素的 \(f_i\) 被保存。我们假设将栈中已有元素。既然栈中元素没有被弹出,那么证明还没有遇到比它大的元素。当我们的元素从栈中弹出的时候,这证明了它发现了第一个比它还要大的数,这道题刚好满足,于是保存 \(f_i\) ,继续算法流程。同理,对于之前的主要问题,我们找到了一个比它还要大的数,说明这个区间结束了。

  1. 单调栈的时间复杂度是?

​ 想一下,我们的每一个元素最多进栈/出栈一次,所以说时间复杂度 O(n)。

  1. 结束标识符

    对于某些特殊题目,栈内元素出不完,会导致答案错误,这时候我们要添加结束标识符强制吐出所有栈内元素。

template <class T, class Cmp = std::greater_equal<T>> // >=
class MonotonyStack
    : private std::stack<std::pair<T, int>, std::vector<std::pair<T, int>>> {
    static constexpr Cmp cmp = Cmp();

public:
    MonotonyStack(const T &val, int id) {//一开始要放入的极限元素
        this->emplace(val, id);
    }

    int push(const T &val, int id) {
        while (this->empty() == false and cmp(this->top().first, val)) {
            this->pop();
        }

        assert(this->empty() == false);
        int result = this->top().second;

        this->emplace(val, id);

        return result;
    }
};

板题

P5788 【模板】单调栈 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

https://www.luogu.com.cn/problem/solution/P5788

题意

即求每一个数的下一个更大数的下标。

思路

  • 维护一个单调递减的栈,即栈顶永远是大于等于目前元素的最小值
  • 每次插入元素与栈顶进行比较
    • 大于等于栈顶
      • 栈顶不停出栈,直到该元素小于栈顶或栈为空。
      • 出栈的每一个栈顶,对应的答案都是该元素。
    • 小于栈顶
      • 入栈

时间复杂度

从出栈入栈的角度切入分析,所有元素都只会出栈一次,入栈一次,所以复杂度就是 \(\operatorname O(n)\)

代码

template <class T, class Cmp = std::greater_equal<T>> // >=
class MonotonyStack
    : private std::stack<std::pair<T, int>, std::vector<std::pair<T, int>>> {
    static constexpr Cmp cmp = Cmp();

public:
    MonotonyStack(const T &val, int id) {
        this->emplace(val, id);
    }

    int push(const T &val, int id) {
        while (this->empty() == false and cmp(this->top().first, val)) {
            this->pop();
        }

        assert(this->empty() == false);
        int result = this->top().second;

        this->emplace(val, id);

        return result;
    }
};

void solve()
{
// #define tests
    int n; std::cin >> n;
    std::vector<int> a(n); for (auto& ai : a) {std::cin >> ai;}
    MonotonyStack<int, std::less_equal<int>> monostack(1E9, -1);
    std::vector<int> res(n);
    for (int i = n - 1; i >= 0; i--) {
        res[i] = monostack.push(a[i], i);
    }
    for (auto& x : res) {std::cout << x + 1 << ' ';} std::cout << '\n';
}

最大矩形面积

D-Largest Rectangle in a Histogram_0x11 基本数据结构-栈 (nowcoder.com)

思路

考虑维护每一个元素 \(i\) 左端第一个比它小的下标 \(l_i\) 及右端第一个比它小的下标 \(r_i\)

显然可能的面积就是 \(a_i \times (r_i - l_i - 1)\)

代码

#include <bits/stdc++.h>

using namespace std;

int main() {
    int n;
    while(cin >> n && n) {
        vector<int> a(n + 2);
        vector<int> l(n + 2), r(n + 2);
        for (int i = 1; i <= n; i++) cin >> a[i];
        a[n + 1] = 0;
        long long ans = 0;
        stack<int> st;
        for (int i = 1; i <= n; i++) {
            while(!st.empty() && a[i] <= a[st.top()]) {
                st.pop();
            }
            if (!st.empty()) l[i] = st.top();
            else l[i] = 0;
            st.push(i);
        }
        while(!st.empty()) st.pop();
        for (int i = n; i; i--) {
            while(!st.empty() && a[i] <= a[st.top()]) {
                st.pop();
            }
            if (!st.empty()) r[i] = st.top();
            else r[i] = n + 1;
            st.push(i);
        }
        for (int i = 1; i <= n; i++) {
            ans = max(ans, 1LL * a[i] * (r[i] - l[i] - 1));
        }
        cout << ans << endl;
    }
    return 0;
}

CF1730C

Minimum Notation - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意

给定一个仅包含 \(0\)\(9\) 这几个数字的字符串,你可以执行以下操作任意次:

选择字符串中的一个数字 \(d\),将 \(d\) 删除后,向字符串任意位置插入一个数字 \(\min(d+1,9)\)

求能够得到的字典序最小的字符串。

思路

想要让字典序最小,就要尽可能将小的数放在前面。每次执行一次操作后,我们都会让一个数变大,只有变大后把它放在字符串后面,让一个更小的数能到前面来,他才能更优。

维护一个单调栈,将弹出的元素 \(d=\min(d+1,9)\) 全部存入一个 vector 中,将所有元素排序。最后输出答案,在输出单调栈中元素的同时,输出 vector 中与之相等的元素,因为已经排完序了,直接从前向后扫即可。

void solve() {
    string s;
    stack<char> st;
    vector<char> els,ans;
    cin >> s;
	int len = s.length();
	for(int i = 0;i < len;i++) {
		while(!st.empty() && st.top() > s[i]) {
			els.push_back(min((char)(st.top() + 1),'9'));
			st.pop();
		}
		st.push(s[i]);
	}
	while(!st.empty()) {
		ans.push_back(st.top());
		st.pop();
	}
	sort(els.begin(),els.end());
	int pos = 0;
	for(int i = ans.size() - 1;i >= 0;i--) {
		cout << ans[i];
		while(pos < els.size() && els[pos] >= ans[i] 
        && els[pos] <= ans[i-1] && i > 0) cout << els[pos++];
	}
	for(int i = pos;i < els.size();i++) cout << els[i];
	cout << endl;
	return;
}
posted @ 2024-01-20 10:34  加固文明幻景  阅读(7)  评论(0编辑  收藏  举报