珂朵莉树
- 结构体
node
中v
用mutable
修饰,访问容器set
时迭代器const
修饰,只读,通过该mutable
使得整个结构体只读时该成员仍可变,为后面区间加操作铺垫。 split()
将区间从pos
处断开,并返回以pos
为左端点的迭代器。这里首先调用set
自身的二分方法lower_bound()
找到左端点不小于pos
的区间,找到后判断it->l == pos
,若满足则不需要断开区间,直接返回;否则pos
在前面一个区间中,将该区间断开为node(L, pos - 1, V)
和node(pos, R, V)
,重新插入容器,insert
方法成功插入后返回迭代器,返回该对象。assign()
区间赋值,这里先split(r+1)
获取结束端点是因为(r+1)
可能在begin
指向区间,操作使迭代器begin
失效。之后erase(begin,end)
,插入新区间即可。
struct node {
ll l ,r;
mutable ll v;
node(ll l, ll r, ll v) : l(l), r(r), v(v) {}
bool operator<(const node &a) const { return l < a.l; }
};
set<node> tree;
set<node>::iterator split(ll pos) {
auto it = tree.lower_bound(node(pos, 0, 0));
if (it != tree.end() && it->l == pos)
return it;
it--;
ll L = it->l, R = it->r, V = it->v;
tree.erase(it);
tree.insert(node(L, pos - 1, V));
return tree.insert(node(pos, R, V)).first;
}
void assign(ll l, ll r, ll v) {
auto end = split(r + 1), begin = split(l);
tree.erase(begin, end);
tree.insert(node(l, r, v));
}
单调栈
单调栈的作用:
- 线性的时间复杂度
- 单调递增栈 可以找到数组中往左/往右第一个比当前元素严格小 < < <(或者不小于 ≥ \geq ≥)的元素
- 从压栈的角度理解,从左至右压入单调栈,将元素 x x x压栈时,此时栈顶元素 t o p top top是往左第一个比 x x x小的元素,因为不存在元素 y y y在 t o p top top右侧且比 x x x小, y y y一定会成为新的栈顶,这和 t o p top top是栈顶相矛盾
- 也可以从弹栈的角度理解,元素 x x x第一个弹出元素 y y y是 x x x往左第一个不小于它的元素,反过来,元素 x x x是 y y y往右第一个小于它的元素
- 可能有些元素不存在以上关系
- 单调递减栈 可以找到数组中往左/往右第一个比当前元素严格大 > > >(或者不大于 ≤ \leq ≤)的元素
- 可以求得以当前元素为最值的最大区间
代码模板
- 最大矩形问题:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
- 数组
R,L
的含义是往左/右最后一个不小于当前元素的元素索引,这个可以通过找到左右第一个小于当前元素的元素,索引 ± 1 \pm1 ±1得到 - 弹栈时,栈顶元素
h[stk.top()]
往右第一个小于它的元素是h[i]
;压栈时,h[i]
往左第一个小于它的元素是h[stk.top()]
- 压栈时,栈空代表左侧没有元素小于
h[i]
,因此L[i]=0
;元素最后仍然在栈中,R[stk.top]=n-1
int R[N], L[N];
int largestRectangleArea(vector<int>& h) {
int n = h.size();
stack<int> stk;
for (int i = 0; i < n; i++) {
while (!stk.empty() && h[stk.top()] >= h[i]) {
R[stk.top()] = i - 1;
stk.pop();
}
if (!stk.empty()) L[i] = stk.top() + 1;
else L[i] = 0;
stk.push(i);
}
while (!stk.empty()) {
R[stk.top()] = n - 1;
stk.pop();
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans = max(ans, h[i] * (R[i] - L[i] + 1));
}
return ans;
}
单调队列
- 滑动窗口最大值问题:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。
- 可能存在同时求最大最小值的情况,为了提高代码复用性,将单调队列封装为
find()
方法 - 单调递减栈可以维护区间最大值,我们将序列元素全部取反,又可以维护最小值,记录答案时通过参数
opt
还原序列
vector<int> find(int opt, vector<int>& a, int k) {
deque<int> Q;
int n = a.size();
vector<int> ans;
for (int i = 0; i < n; i++) {
while (!Q.empty() && Q.front() < i - k + 1) Q.pop_front();
while (!Q.empty() && a[Q.back()] < a[i]) Q.pop_back();
Q.push_back(i);
if (i >= k - 1) {
ans.push_back(opt * a[Q.front()]);
}
}
return ans;
}
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
return find(1, nums, k);
}