单调栈
模板
单调栈,可以用来求 左边/右边
第一个 大于/小于/大于等于/小于等于
当前元素的下标的位置。
模板说明 :
1,求左边 :从左往右遍历 ;求右边 :从右往左遍历
2,求 < a[i] 的元素 :维护严格递增的单调栈, a[i] <= top 作为出栈条件
求 <= a[i] 的元素 : 维护非严格递增的单调栈, a[i] < top 作为出栈条件
求 > a[i] 的元素 : 维护严格递减的单调栈, a[i] >= top 作为出栈条件
求 >= a[i] 的元素 : 维护非严格递减的单调栈, a[i] > top 作为出栈条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| vector<int> LeftAndStrictlyLess(int* a, int n) { vector<int> lf(n + 1); stack<int> stk; for(int i = 1; i <= n; i ++) { while(stk.size() && a[i] <= a[stk.top()]) { stk.pop(); } lf[i] = (stk.size() ? stk.top() : -1); stk.push(i); } return lf; }
vector<int> RightAndStrictlyGreater(int *a, int n) { vector<int> rt(n + 1); stack<int> stk; for(int i = n; i >= 1; i --) { while(stk.size() && a[i] <= a[stk.top()]) { stk.pop(); } rt[i] = (stk.size() ? stk.top() : -1); stk.push(i); } return rt; }
|
单调栈与DP结合
给定一个整数数组 arr
,找到 min(b)
的总和,其中 b
的范围为 arr
的每个(连续)子数组。
由于答案可能很大,因此 返回答案模 10^9 + 7
。
因为是连续子数组,考虑每个点作为右端点的所有连续子数组的 min(b)
的总和,设为 $f(x)$ 。
发现对于位置 $i$ 的元素 $a_i$ 来说,设 $lf(i)$ 表示 $a_i$ 左边第一个 $<a_i$ 的元素的位置。那么 $[lf(i)+1,i]$ 作为左端点时,min(b)
都是 $a_i$ ,因为 $a_i$ 大于等于这些元素。对于 $lf(i)$ 及其左边的位置,发现答案就是 $f(lf(i))$ 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Solution { public: const int MOD = 1E9 + 7; vector<int> RightAndStrictlyGreater(vector<int> a, int n) { vector<int> rt(n); stack<int> stk; for(int i = 0; i < n; i ++) { while(stk.size() && a[i] <= a[stk.top()]) { stk.pop(); } rt[i] = (stk.size() ? stk.top() : -1); stk.push(i); } return rt; }
int sumSubarrayMins(vector<int>& arr) { int n = arr.size(); vector<int> f(n, 0); vector<int> lf = RightAndStrictlyGreater(arr, n); for(int i = 0; i < n; i ++) { if(lf[i] != -1) f[i] += f[lf[i]]; (f[i] += (i - lf[i]) * arr[i]) %= MOD; } int res = 0; for(int i = 0; i < n; i ++) { (res += f[i]) %= MOD; } return res; } };
|
经典 Trick 悬线法
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
这个问题比较显然,作为例题二的引入。
显然对于答案的矩形来说,它的高一定是其中一个柱子的高,因为如果比所有柱子都矮,答案还可以加一。
那么对于每个柱子 $h_i$ ,我们维护每个柱子左边第一个 $<h_i$ 的位置 $lf(i)$ ,右边第一个 $>h_i$ 的位置 $rt(i)$ 。
那么对于柱子 $i$ ,他就可以跟 $[lf(i)+1,rt(i)-1]$ 的这些位置的柱子结合为一个矩形,高度为 $h_i$ 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Solution { public: int largestRectangleArea(vector<int>& heights) { int n = heights.size(); vector<int> lf(n), rt(n); stack<int> stk; for(int i = 0; i < n; i ++) { while(stk.size() && heights[i] <= heights[stk.top()]) { stk.pop(); } lf[i] = (stk.size() ? stk.top() : -1); stk.push(i); } while(stk.size()) stk.pop(); for(int i = n - 1; i >= 0; i --) { while(stk.size() && heights[i] <= heights[stk.top()]) { stk.pop(); } rt[i] = (stk.size() ? stk.top() : n); stk.push(i); } while(stk.size()) stk.pop(); int maxArea = 0; for(int i = 0; i < n; i ++) { maxArea = max(maxArea, heights[i] * (rt[i] - lf[i] - 1)); } return maxArea; } };
|
给定一个仅包含 0
和 1
、大小为 rows x cols
的二维二进制矩阵,找出只包含 1
的最大矩形,并返回其面积。
考虑到答案矩阵一定包含一条完整的竖线。
考虑经典的技巧,悬线法。
设 $h(i,j)$ 表示从下标 $(i,j)$ 向上延伸的最大长度,要求这条竖线全部是 1
。
那么我们枚举行,对于每一行的列,假设来到了 $(x,y)$ 这个点,对于 $x$ 这一列,用单调栈维护 $h(x,y)$ 向两侧延伸的最大矩形长度,一定能枚举到答案矩形。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| class Solution { public: int n, m, res = 0; vector<vector<int> > hangLine;
void work(int row) { vector<int> lf(m), rt(m); stack<int> stk; for(int j = 0; j < m; j ++) { while(stk.size() && hangLine[row][j] <= hangLine[row][stk.top()]) { stk.pop(); } lf[j] = (stk.size() ? stk.top() : -1); stk.push(j); } while(stk.size()) { stk.pop(); } for(int j = m - 1; j >= 0; j --) { while(stk.size() && hangLine[row][j] <= hangLine[row][stk.top()]) { stk.pop(); } rt[j] = (stk.size() ? stk.top() : m); stk.push(j); } while(stk.size()) { stk.pop(); } for(int j = 0; j < m; j ++) { res = max(res, hangLine[row][j] * (rt[j] - lf[j] - 1)); } }
int maximalRectangle(vector<vector<char>>& matrix) { n = matrix.size(), m = matrix[0].size(); hangLine.resize(n); for(int i = 0; i < n; i ++) { hangLine[i].resize(m); } for(int i = 0; i < n; i ++) { for(int j = 0; j < m; j ++) { if(matrix[i][j] == '1') { hangLine[i][j] = (i ? hangLine[i - 1][j] + 1 : 1); } } } for(int i = 0; i < n; i ++) { work(i); } return res; } };
|
双倍经验 :P4147 玉蟾宫
练习题
奶牛在熊大妈的带领下排成了一条直队。
显然,不同的奶牛身高不一定相同……
现在,奶牛们想知道,如果找出一些连续的奶牛,要求最左边的奶牛 $A$ 是最矮的,最右边的 $B$ 是最高的,且 $B$ 高于 $A$ 奶牛。中间如果存在奶牛,则身高不能和 $A,B$ 奶牛相同。问这样的奶牛最多会有多少头?
从左到右给出奶牛的身高,请告诉它们符合条件的最多的奶牛数(答案可能是 $0,2$,但不会是 $1$)。
枚举右端点 $i$ ,即 $a_i=B$ ,设第一个 $\geq B$ 的位置为 $lf(i)$ ,那么 $[lf(i)+1,i-1]$ 这些位置是左端点的备选位置。
我们可以枚举这些点,设第一个 $\leq a_i$ 的位置为 $rt(i)$ ,那么只要 $rt(i)>i$ ,这个位置就是合法的。这样就得到了解法一,显然这种时间复杂度是难以解释的,虽然可以通过题目,但是我们还是希望得到拥有明确时间复杂度的解法。
考虑到 $[lf(i)+1,i-1]$ 这些位置里面,取一个最靠左的位置,满足这个位置到右端点之间,该位置的数是唯一最小值。
不动脑的想,可以维护 ST 表求最小值,最小值最靠右的位置可以用线段树求区间值域最靠右位置,这里的值域当然就是一个点了。
但是这种数据结构太大了,大过题目本身。其实就是求最靠左的最小值,而且这个最小值是最靠右最小值。我们维护一个单调严格递增栈即可,这样对栈的下标二分,分出来最左边的位置,因为是严格递增,该位置一定是最靠右的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| constexpr int N = 1e5 + 10; stack<int> stk1, stk2; int n, ls[N], rs[N], a[N], ans = 0;
void solve() { cin >> n; for(int i = 1; i <= n; i ++) { cin >> a[i]; }
for(int i = 1; i <= n; i ++) { while(stk1.size() && a[i] > a[stk1.top()]) { stk1.pop(); } ls[i] = (stk1.size() ? stk1.top() : 0); stk1.push(i); }
for(int i = n; i >= 1; i --) { while(stk2.size() && a[i] < a[stk2.top()]) { stk2.pop(); } rs[i] = (stk2.size() ? stk2.top() : n + 1); stk2.push(i); }
for(int i = 1; i <= n; i ++) { for(int j = ls[i] + 1; j < i; j ++) { if(ans >= i - j + 1) break; if(rs[j] > i) { ans = i - j + 1; } } } cout << ans << '\n'; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <bits/stdc++.h> using namespace std; const int N = 1E5 + 10;;
int n, ans, tx, tn; int a[N], sx[N], sn[N];
int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", a + i); for (int i = 1; i <= n; ++i) { while (tn && a[i] <= a[sn[tn]]) -- tn; while (tx && a[i] > a[sx[tx]]) -- tx; int k = upper_bound(sn + 1, sn + 1 + tn, sx[tx]) - sn; if (k != (tn + 1)) { ans = max(ans, i - sn[k] + 1); } sn[++ tn] = i; sx[++ tx] = i; } printf("%d\n", ans); return 0; }
|