Y
K
N
U
F

题解:P13933 [蓝桥杯 2022 省 Java B] 最大子矩阵

大家好,我又来了,不过我还是不会单调队列。这次是线段树套 ST 表。

不同之处是这次的做法跑得很快。

正文

还是迫真的一句话题意

求所有满足矩形区域内最大值和最小值的差不大于 \(lim\) 的矩形区域的面积的最大值。

解析

仍旧是暴力枚举矩形的高,然后矩形的宽用双指针枚举。

枚举矩形又做到了 \(O(n^2m)\) 的复杂度,对吧。

然后就又是这个问题,如何得到矩形区域内的最大最小值?

不知从何而来的前世记忆告诉我们要用 K-Dtree。不过,算了吧,慢死了。

我们选择用线段树套 ST 表来维护,考虑线段树维护 \(n\) 这一维度,也就是维护高,然后每一个节点都开一个 ST 表,这样一个 ST 表维护的范围就是这个线段树节点维护的高的范围乘以整个矩形的宽。

这样的复杂度是更加优秀的,建树和 ST 表的复杂度发现是 \(O(nm\log m)\),而查询的复杂度是 \(O(\log n)\) 的。做了 \(O(n^2m)\) 次查询,所以总复杂度来到了 \(O(n^2m\log n)\),跑得很快,比 K-Dtree 快多了。

唯一注意不可以开静态空间,因为虽然空间是 \(O(nm\log m)\) 的,但是因为数据范围比较奇特,开静态也就是按 \(n,m\) 的最大值开是会爆炸的。

代码

// code by 樓影沫瞬_Hz17
#include <bits/stdc++.h>
using namespace std;
 
#define getc() getchar_unlocked()
#define putc(a) putchar_unlocked(a)
#define en_ putc('\n')
#define e_ putc(' ')
 
using pii = pair<int, int>;
 
template<class T> inline T in() { 
	T n = 0; char p = getc();
	while(p < '-') p = getc();
	bool f = p == '-' ? p = getc() : 0;
	do n = n * 10 + (p ^ 48), p = getc();
	while(isdigit(p));
	return f ? -n : n;
}
template<class T> inline T in(T &a) { return a = in<T>(); }
template<class T, class ... Args> inline void in(T &t, Args&... args) { in(t), in(args...); }
 
template<class T> inline void out(T n) {
	if(n < 0) putc('-'), n = -n;
	if(n > 9) out(n / 10);
	putc(n % 10 + '0');
}
 
template<class T1, class T2> T1 max(T1 a, T2 b) { return a > b ? a : a = b;}
template<class T1, class T2> T1 min(T1 a, T2 b) { return a < b ? a : a = b;}
 
constexpr int N = 82, M = 100000 + 10;

int n, m, a[N][M], lim, ans;

struct ST {
	vector<int> f[17]; // 没错的,要开动态空间,vector 当然是首选
	vector<int> g[17];
	void init() {
		for(int i = 0; i < 17; i ++) {
			f[i].reserve(m + 10);
			g[i].reserve(m + 10);
		}
	}
	void build(int pos) { // 对于高上的单点建立 ST 表
		for(int i = 1; i <= m; i ++) 
			g[0][i] = f[0][i] = a[pos][i];
		for(int i = 1; i <= 16; i ++) 
			for(int j = 1; j <= m; j ++) {
				f[i][j] = max(f[i - 1][j], f[i - 1][min(m, j + (1 << (i - 1)))]);
				g[i][j] = min(g[i - 1][j], g[i - 1][min(m, j + (1 << (i - 1)))]);
			}
	}
	pii que(int l, int r) { // ST 表查询
		int k = __lg(r - l + 1);
		return {max(f[k][l], f[k][r - (1 << k) + 1]), min(g[k][l], g[k][r - (1 << k) + 1])};
	}
}; 

struct node { // 线段树节点
	ST val;
	int l, r;
} t[N * 4];
int cnt, rt;

inline void up(int u) { // 合并两个区间的 ST 表
	ST &U = t[u].val, &L = t[t[u].l].val, &R = t[t[u].r].val;
	for(int i = 0; i <= 16; i ++) 
		for(int j = 1; j <= m; j ++) {
			U.f[i][j] = max(L.f[i][j], R.f[i][j]);
			U.g[i][j] = min(L.g[i][j], R.g[i][j]);
		}
}

#define mid ((l + r) >> 1)
inline void build(int&u, int l, int r) { // 线段树建树
	u = ++ cnt;
	t[u].val.init();
	if(l == r) return t[u].val.build(l);
	build(t[u].l, l, mid);
	build(t[u].r, mid + 1, r);
	up(u);
}

inline pii que(int u, int l, int r, int L, int R, int ll, int rr) { // 线段树查询
	if(L <= l and r <= R) return t[u].val.que(ll, rr); // 配合 ST 表查询
	if(L <= mid and R > mid) {
		auto [mx1, mi1] = que(t[u].l, l, mid, L, R, ll, rr);
		auto [mx2, mi2] = que(t[u].r, mid + 1, r, L, R, ll, rr);
		return {max(mx1, mx2), min(mi1, mi2)};
	}
	if(L <= mid) return que(t[u].l, l, mid, L, R, ll, rr);
	if(L > mid) return que(t[u].r, mid + 1, r, L, R, ll, rr);
}

signed main() {
	#ifndef ONLINE_JUDGE
		freopen("in.ru", "r", stdin);
		freopen("out.ru", "w", stdout);
	#endif
	in(n, m);
	for(int i = 1; i <= n; i ++) 
		for(int j = 1; j <= m; j ++) in(a[i][j]);
	in(lim);
	
	build(rt, 1, n);

	for(int l = 1; l <= n; l ++) {
		for(int r = l; r <= n; r ++) { // 枚举第一维
			for(int ll = 1, rr = 1; rr <= m; ) { // 双指针
				while(rr <= m and (r - l + 1) * (rr - ll + 1) <= ans) rr ++; 
				if(rr > m) break; // 前世记忆的剪枝
				auto [mx, mi] = que(rt, 1, n, l, r, ll, rr);
				if(mx - mi > lim) ll ++, rr ++;
				else ans = max(ans, (r - l + 1) * (rr - ll + 1)), rr ++;
			}
		}
	}

	out(ans);
}   
// 星間~ 干渉~ 融解~ 輪迴~ 邂逅~ 再生~ ララバイ~
posted @ 2025-11-05 22:04  樓影沫瞬_Hz17  阅读(8)  评论(0)    收藏  举报