单调栈、单调队列、优先队列(堆)

单调栈

单调栈世界复杂度 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;
}

Maximal submatrix

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),核心思想是在决策集合(队列)中及时排除一定不是最优解的选择

洛谷 P1886 滑动窗口 /【模板】单调队列

在这里插入图片描述

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;
}

Acwing 135最大子序和

不超过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;
    }
};

An Easy Problem

给定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;
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值