二维单调队列 CF1195 OpenStreetMap (CF #574)

本文介绍了一种利用二维单调队列解决特定矩阵问题的方法。通过先对每行应用单调队列,再对每列应用,可以有效计算固定大小子矩阵的最小值。以CF1195E OpenStreetMap题目为例,详细展示了如何使用二维单调队列进行高效求解。

二维单调队列

在处理二维的单调队列问题的时候
先将每行做一遍单调队列
再对每列做一遍单调队列,就能实现二维的单调队列

CF1195E OpenStreetMap

题意

给出一个矩阵和a,b
计算所有大小为a*b的子矩阵的最小值

分析

由于子矩阵的大小是固定的
我们可以直接用二维单调队列维护子矩阵的最小值
先对每行做单调队列
再对每列做单调队列

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 3005;
int a[maxn][maxn];
int n, m, r, c, g, x, y, z;
void Pre_Work() {
	scanf("%d%d%d%d", &n, &m, &r, &c);
	scanf("%d%d%d%d", &g, &x, &y, &z);
	for (int i = 1; i <= n; i++) {
		a[i][1] = g;
		for (int j = 2; j <= m; j++)
			a[i][j] = (LL(a[i][j - 1]) * x + y) % z;
		g = (LL(a[i][m]) * x + y) % z;
	}
}
int q[maxn], b[maxn][maxn];
void R_Queue(int i) {
	int head = 1, tail = 0;
	for (int j = 1; j < c; j++) {
		while (tail >= head && a[i][q[tail]] >= a[i][j]) tail--;
		q[++tail] = j;
	}
	for (int j = c; j <= m; j++) {
		while (tail >= head && a[i][q[tail]] >= a[i][j]) tail--;
		q[++tail] = j;
		while (tail >= head && q[head] < j - c + 1)head++;
		b[i][j] = a[i][q[head]];
	}
}
void C_Queue(int j) {
	int head = 1, tail = 0;
	for (int i = 1; i < r; i++) {
		while (tail >= head && b[q[tail]][j] >= b[i][j])tail--;
		q[++tail] = i;
	}
	for (int i = r; i <= n; i++) {
		while (tail >= head && b[q[tail]][j] >= b[i][j]) tail--;
		q[++tail] = i;
		while (tail >= head && q[head] < i - r + 1)head++;
		a[i][j] = b[q[head]][j];
	}
}
int main() {
	Pre_Work();
	for (int i = 1; i <= n; i++)
		R_Queue(i);
	for (int i = c; i <= m; i++)
		C_Queue(i);
	LL ans = 0;
	for (int i = r; i <= n; i++)
		for (int j = c; j <= m; j++)
			ans += a[i][j];
	printf("%I64d\n", ans);
}
### 关于单调队列的概念 单调队列是一种特殊的队列,在这种队列中的元素按照某种特定顺序排列,即保持单调递增或单调递减的关系。对于单调队列而言,其特性在于能够高效地维护一段区间内的最值问题,这得益于可以在队首和队尾都执行出队操作而仅允许在队尾进行入队操作[^3]。 ### 单调队列的实现方法 为了更好地理解如何实现单调队列,下面给出了一种基于双端队列(deque)来构建单调队列的方式: ```cpp #include <iostream> #include <deque> using namespace std; class MonotonicQueue { private: deque<int> data; public: void push(int n) { // 维护一个非严格递减序列 while (!data.empty() && data.back() < n) { data.pop_back(); } data.push_back(n); } int max() const { // 获取当前的最大值 return data.front(); } void pop(int n) { // 如果要移除的是最大值,则真正弹出;否则只是逻辑上的过期 if (!data.empty() && data.front() == n) { data.pop_front(); } } }; ``` 上述代码展示了怎样利用C++标准库中的`<deque>`容器创建一个维持内部数值按降序存储的单调队列类`MonotonicQueue`。每当有新数加入时(`push`),会将所有小于该数的老成员清除掉以确保整体呈下降趋势;当询问最大值(`max`)的时候总是返回第一个位置处保存的那个较大者;最后是从前部删除指定项(`pop`)的操作,只会在确实等于现存最高位的情况下才实际减少长度。 ### 单调队列的应用场景 单调队列常被应用于解决滑动窗口内求极值的问题以及其他涉及连续子数组/子串性质计算的任务中。例如在一个固定大小K的滑窗沿数组A移动的过程中寻找每一轮次里边最大的那个数字,这时就可以借助预先准备好的单调队列快速定位到目标答案而不必每次都重新遍历整个范围去比较找出最优解[^2]。 #### 滑动窗口最大值实例解析 考虑这样一个具体案例——给定整型列表nums=[1,3,-1,-3,5,3,6,7]以及宽度k=3的要求下,希望得到各阶段对应的峰值集合res=[3,3,5,5,6,7]。此时可采用如下策略完成任务: - 初始化一个空闲状态下的单调队列; - 遍历输入源的同时不断更新并查询瞬态极大值直至结束条件达成为止。 这种方法不仅提高了效率还简化了编程难度,非常适合处理此类动态规划难题。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值