P9905 [COCI 2023/2024 #1] AN2DL 题解

思路

首先考虑暴力算法,枚举每一个右端点,再遍历求最大值,枚举的时间复杂度为 O ( n 2 ) O(n^2) O(n2),遍历复杂度同样 O ( n 2 ) O(n^2) O(n2)。显然,在 n = 3000 n=3000 n=3000 时无法通过,考虑更优解法。

大眼观察,发现这道题和 P1886 滑动窗口 /【模板】单调队列 是几乎一样的;也就是把一维单调队列变成了二维的单调队列。

当然,也可以理解成多个窗口在二维矩阵上面滑动;也就是一个 r × s r\times s r×s 的滑动窗口。

分析发现,这种算法是可以通过的,我们不妨分步解决,跑两次单调队列就可以完美解决问题!

操作流程:

n × m n\times m n×m 的矩阵每行都跑一次单调队列。

跑完后可以发现形成了一个 n × ( m − s + 1 ) n\times (m-s+1) n×(ms+1) 的矩阵 A A A,根据题意,不难发现, A i , j = max ⁡ j ≤ y ≤ ( j + s − 1 ) × C i , y A_{i,j}= \max\limits_{j\le y \le (j+s-1)}^{}\times C_{i,y} Ai,j=jy(j+s1)max×Ci,y(设 C C C 为题目输入的矩阵)

A A A 矩阵每列都跑一次单调队列(这样就形成了一个二维的单调队列结果)。

跑完后可以发现形成了一个 ( n − r + 1 ) × ( m − s + 1 ) (n-r+1)\times (m-s+1) (nr+1)×(ms+1) 的矩阵 B B B,根据题意,不难发现, B i , j = max ⁡ i ≤ x ≤ ( i + r − 1 ) × A x , y B_{i,j}= \max\limits_{i\le x \le (i+r-1)}^{}\times A_{x,y} Bi,j=ix(i+r1)max×Ax,y

此时的 B B B 矩阵就是所期望的啦!

代码:

_zuoqingyuan的代码基础上优化了码风,并增加了细节注释。

#include <bits/stdc++.h>
using namespace std;
int a[4005][4005], n, m, r, s; // a表示原始矩阵,n和m表示矩阵的行列数,r和s表示滑动窗口的大小。
vector <int> v1[4005], v2[4005]; // v1和v2用来存储中间结果和最终结果。
deque <int> q; // 双端队列用来维护滑动窗口。

int main() {
	ios :: sync_with_stdio(false); // 禁用cin和cout的同步,提高I/O效率。
	cin >> n >> m; // 输入矩阵的行和列。
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= m; j ++) cin >> a[i][j]; // 输入矩阵元素。
	cin >> r >> s; // 输入滑动窗口的大小。
	
	for (int i = 1; i <= n; i ++) {
		v1[i].push_back(0); // 初始化v1的每一行。
		for (int j = 1; j <= m; j ++) {
			while (q.size() && q.front() <= j - s) q.pop_front(); // 若队列头部的元素已不在窗口内,弹出。
			while (q.size() && a[i][q.back()] < a[i][j]) q.pop_back(); // 若当前元素大于队尾元素,弹出队尾,维持队列单调递减。
			q.push_back(j); // 将当前列加入队列。
			if (j >= s) v1[i].push_back(a[i][q.front()]); // 若已到窗口大小,加入v1中。
		}
		q.clear(); // 清空队列为下一行做准备。
	}
	m = m - s + 1; // 更新列的有效长度。
	
	for (int i = 1; i <= m; i ++) {
		v2[i].push_back(0); // 初始化v2的每一列。
		for (int j = 1; j <= n; j ++) {
			while (q.size() && q.front() <= j - r) q.pop_front(); // 类似上面的操作,但是这次是行。
			while (q.size() && v1[q.back()][i] < v1[j][i]) q.pop_back(); // 维持队列单调递减。
			q.push_back(j); // 将当前行加入队列。
			if (j >= r) v2[i].push_back(v1[q.front()][i]); // 若已到窗口大小,加入v2中。
		}
		q.clear(); // 清空队列为下一列做准备。
	}
	n = n - r + 1; // 更新行的有效长度。
	
	// 输出最终结果。
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= m; j ++) cout << v2[j][i] << endl;
		cout << endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值