单调栈
单调栈世界复杂度 O ( n ) O(n) O(n),核心思想在于及时排除不可能的选项,保持策略集合的高度有效性和秩序性。
最大矩形面积 蓝书P53
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
8
4000
#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;
const int N = 1e5;
#define int long long
int nums[N + 5], l[N + 5], r[N + 5];
signed main() {
int n;
while (scanf("%lld", &n) && n) {
for (int i = 1; i <= n; ++i) {
scanf("%lld", nums + i);
}
stack<int> s1, s2;
s1.push(0);
s2.push(n + 1);
nums[0] = nums[n + 1] = -1;
for (int i = 1; i <= n; ++i) {
while (nums[s1.top()] >= nums[i]) s1.pop();
l[i] = s1.top();
s1.push(i);
while (nums[s2.top()] >= nums[n - i + 1]) s2.pop();
r[n - i + 1] = s2.top();
s2.push(n - i + 1);
}
int ans = 0;
for (int i = 1; i <= n; ++i) {
ans = max(ans, (i - l[i] + r[i] - i - 1) * nums[i]);
}
printf("%lld\n", ans);
}
return 0;
}
1
2 3
1 2 4
2 3 3
4
求出最大的子矩阵的面积,要求子矩阵的每一列的元素不递减
1 2 4 nums
2 3 3
1 1 1 mat 以(i, j)为柱形图的底,最高不递减高度
2 2 1
转换为二维单调栈(n行也就是跑n次单调栈,取最大值)
#include <iostream>
#include <cstdio>
#include <stack>
#include <cstdlib>
using namespace std;
#define INF 0x7f7f7f7f
const int N = 2e3;
int nums[N + 5][N + 5], mat[N + 5][N + 5];
int main() {
int t, n, m;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%d", &nums[i][j]);
}
}
for (int j = 1; j <= m; ++j) {
for (int i = 1; i <= n; ++i) {
if (nums[i][j] >= nums[i - 1][j]) mat[i][j] = mat[i - 1][j] + 1;
else mat[i][j] = 1;
}
}
int *l = (int *)malloc(sizeof(int) * (m + 5));
int *r = (int *)malloc(sizeof(int) * (m + 5));
int ans = 0;
for (int i = 1; i <= n; ++i) {
stack<int> s1, s2;
s1.push(0);
s2.push(m + 1);
mat[i][0] = mat[i][m + 1] = -1;
for (int j = 1; j <= m; ++j) {
while (mat[i][s1.top()] >= mat[i][j]) s1.pop();
l[j] = s1.top();
s1.push(j);
while (mat[i][s2.top()] >= mat[i][m - j + 1]) s2.pop();
r[m - j + 1] = s2.top();
s2.push(m - j + 1);
}
for (int j = 1; j <= m; ++j) {
ans = max(ans, (r[j] - l[j] - 1) * mat[i][j]);
}
}
free(l);
free(r);
printf("%d\n", ans);
}
return 0;
}
单调队列
时间复杂度 O ( n ) O(n) O(n),核心思想是在决策集合(队列)中及时排除一定不是最优解的选择
8 3
1 3 -1 -3 5 3 6 7
-1 -3 -3 -3 3 3
3 3 5 5 6 7
#include <iostream>
#include <cstdio>
#include <deque>
using namespace std;
const int N = 1e6;
int nums[N + 5], ans_min[N + 5], ans_max[N + 5];
struct Node {
int ind, val;
};
int main() {
int n, k;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) {
scanf("%d", nums + i);
}
deque<Node> que1, que2;
for (int i = 1; i <= n; ++i) {
if (i > k && que1.front().ind <= i - k) que1.pop_front();
if (i > k && que2.front().ind <= i - k) que2.pop_front();
while (!que1.empty() && que1.back().val >= nums[i]) que1.pop_back();
while (!que2.empty() && que2.back().val <= nums[i]) que2.pop_back();
que1.push_back(Node{i, nums[i]});
que2.push_back(Node{i, nums[i]});
if (i >= k) {
ans_min[i - k] = que1.front().val;
ans_max[i - k] = que2.front().val;
}
}
for (int i = 0; i <= n - k; ++i) {
i && putchar(' ');
printf("%d", ans_min[i]);
}
puts("");
for (int i = 0; i <= n - k; ++i) {
i && putchar(' ');
printf("%d", ans_max[i]);
}
puts("");
return 0;
}
不超过m的最大字序和
6 4
1 -3 5 1 -2 3
7
在前缀和数组(S)上维护一个m+1长度的单调递增队列(Q)
以i结尾最大子串和=S[i] - S[Q_front]
单调递增队列队首元素为最小值!!!保证前缀最小,即可满足子串和最大
#include <iostream>
#include <cstdio>
#include <deque>
#include <cinttypes>
using namespace std;
const int N = 300000;
int nums[N + 5];
int main() {
int n, m, ans = INT32_MIN; //注意求最大 ans设为极小
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d", nums + i);
nums[i] += nums[i - 1];
}
deque<int> que;
que.push_back(0);
for (int i = 1; i <= n; ++i) {
if (i > m && que.front() < i - m) que.pop_front();
ans = max(ans, nums[i] - nums[que.front()]);
while (!que.empty() && nums[i] <= nums[que.back()]) que.pop_back();
que.push_back(i);
}
printf("%d\n", ans);
return 0;
}
优先队列
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
class MedianFinder {
public:
MedianFinder() {}
priority_queue<int> que_max;
priority_queue<int, vector<int>, greater<int> > que_min;
#define balance(a, b) {\
if (a.size() - b.size() <= 1) return ; \
b.push(a.top());\
a.pop();\
}
void addNum(int num) {
if (que_max.empty()) que_max.push(num);
else {
if (num <= que_max.top()) {
que_max.push(num);
balance(que_max, que_min)
} else {
que_min.push(num);
balance(que_min, que_max)
}
}
return ;
}
double findMedian() {
if ((que_max.size() + que_min.size()) & 1) {
return que_max.size() > que_min.size() ? que_max.top() : que_min.top();
}
return (que_max.top() + que_min.top()) / 2.0;
}
};
给定1~n和1~m两个序列,从两个序列中各取一个元素求积,可以构成n的m次幂个乘积,求第k个大的乘积是多少
3 3 4
4
(n = 3 m = 3 top4 = 4 1,2,2,3,3,4,6,6,9)
把1~n * m 这m个乘积压到优先队列中,并附带m信息
每次弹出最大的元素,并把这个元素用m-1更新,重新压入队列中
重复k-1次操作
蓝书P84同理题,乘积变为和,1~n序列变为自定义有序序列
#include <iostream>
#include <queue>
#include <utility>
using namespace std;
typedef pair<long long, int> PLI;
#define int long long
signed main() {
int n, m, k;
cin >> n >> m >> k;
priority_queue<PLI> que;
if (n > m) swap(n, m);
for (int i = 1; i <= n; ++i) {
que.push(make_pair(i * m, m));
}
while (k - 1) {
int f = que.top().first;
int s = que.top().second;
que.push(make_pair(f / s * (s - 1), s - 1));
que.pop();
--k;
}
cout << que.top().first << endl;
return 0;
}