[LOJ#3177][IOI2019]矩形区域(分治 + 单调栈)

这篇博客介绍了LOJ#3177题目中关于矩形区域的问题解决方法,重点探讨了如何利用分治策略和单调栈来优化算法。文章首先证明了一个性质,即满足特定条件的二元组数量为O(n),然后将这个性质应用于原问题,指出当某一行或一列被强制选择时,合法的区间数量减少。接着,博主详细阐述了以第mid行作为划分的解题思路,包括如何枚举列区间和行区间,并使用单调栈进行合法子矩形的判断。最后,博主提到了使用RMQ进行合法性判定,并表示对于不过第mid行的子矩形,可以进一步使用分治处理,整体复杂度为O(nm(logn+logm))。文章末尾,博主欢迎读者分享O(nm)的解决方案。
摘要由CSDN通过智能技术生成

Address

Solution

  • 首先有一个性质
  • 对于长度为 n n n 的数组 a 1... n a_{1...n} a1...n
  • 满足对于所有 l ≤ i ≤ r l\le i\le r lir 都有 a l − 1 > a i , a r + 1 > a i a_{l-1}>a_i,a_{r+1}>a_i al1>ai,ar+1>ai 的二元组 ( l , r ) (l,r) (l,r) 只有 O ( n ) O(n) O(n)
  • 证明可以使用单调栈
  • 把这个性质放回原问题
  • 相当于如果某一行强制被选出,那么合法的列区间只有 O ( m ) O(m) O(m)
  • 同样地,如果某一列强制被选出,那么合法的行区间也只有 O ( n ) O(n) O(n)
  • m i d = ⌊ 1 + n 2 ⌋ mid=\lfloor\frac{1+n}2\rfloor mid=21+n ,考虑统计过第 m i d mid mid 行的子矩形数量,即第 m i d mid mid 行被强制选出
  • 先利用单调栈枚举出所有合法的列区间 [ l , r ] [l,r] [l,r]
  • 现在要做的就是统计有多少个合法子矩形,行区间包含了 m i d mid mid 且列区间为 [ l , r ] [l,r] [l,r]
  • 列区间为 [ l , r ] [l,r] [l,r] 相当于第 l l l 列到第 r r r 列都被强制选出
  • 故这时合法的行区间只有 O ( n ) O(n) O(n) 个,同样使用单调栈枚举这样的行区间
  • 然后就是判定一个子矩形是否合法了
  • 这个可以各凭本事求,下面的代码里使用的是预处理每个数往上 / 下第一个不小于自己的数所在位置后用 RMQ 判定
  • 而至于不过第 m i d mid mid 行的子矩形,分治处理即可
  • O ( n m ( log ⁡ n + log ⁡ m ) ) O(nm(\log n+\log m)) O(nm(logn+logm))
  • 如果有 O ( n m ) O(nm) O(nm) 做法欢迎提出

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

const int N = 2505, M = N << 1, LogN = 12;

int n, m, a[N][N], top, stk[N], tot, L[M], R[M], le[N][N], ri[N][N],
up[N][N][LogN], dn[N][N][LogN], Log[N], ans;

int upmax(int i, int l, int r)
{
	int k = Log[r - l + 1];
	return Max(up[i][l][k], up[i][r - (1 << k) + 1][k]);
}

int dnmin(int i, int l, int r)
{
	int k = Log[r - l + 1];
	return Min(dn[i][l][k], dn[i][r - (1 << k) + 1][k]);
}

bool avail(int xl, int xr, int yl, int yr)
{
	return dnmin(xl - 1, yl, yr) > xr && upmax(xr + 1, yl, yr) < xl;
}

void solve(int l, int r)
{
	if (l > r) return;
	int mid = l + r >> 1;
	top = tot = 0;
	for (int i = 1; i <= m; i++)
	{
		while (top && a[mid][i] > a[mid][stk[top]])
		{
			if (stk[top] + 1 < i) L[++tot] = stk[top] + 1, R[tot] = i - 1;
			top--;
		}
		if (top && stk[top] + 1 < i) L[++tot] = stk[top] + 1, R[tot] = i - 1;
		stk[++top] = i;
	}
	for (int i = 1; i <= tot; i++)
	{
		top = 0; int x = L[i], il = mid, ir = mid;
		while (il > l - 1 && ri[il][L[i] - 1] > R[i]
			&& le[il][R[i] + 1] < L[i]) il--;
		while (ir < r + 1 && ri[ir][L[i] - 1] > R[i]
			&& le[ir][R[i] + 1] < L[i]) ir++;
		for (int j = il; j <= ir; j++)
		{
			while (top && a[j][x] > a[stk[top]][x])
			{
				if (stk[top] + 1 <= mid && j - 1 >= mid
					&& avail(stk[top] + 1, j - 1, L[i], R[i])) ans++;
				top--;
			}
			if (top && stk[top] + 1 <= mid && j - 1 >= mid
				&& avail(stk[top] + 1, j - 1, L[i], R[i])) ans++;
			stk[++top] = j;
		}
	}
	solve(l, mid - 1); solve(mid + 1, r);
}

int main()
{
	read(n); read(m);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			read(a[i][j]);
	for (int i = 1; i <= n; i++)
	{
		stk[top = 0] = 0;
		for (int j = 1; j <= m; j++)
		{
			while (top && a[i][j] > a[i][stk[top]]) top--;
			le[i][j] = stk[top]; stk[++top] = j;
		}
		stk[top = 0] = m + 1;
		for (int j = m; j >= 1; j--)
		{
			while (top && a[i][j] > a[i][stk[top]]) top--;
			ri[i][j] = stk[top]; stk[++top] = j;
		}
	}
	for (int j = 1; j <= m; j++)
	{
		stk[top = 0] = 0;
		for (int i = 1; i <= n; i++)
		{
			while (top && a[i][j] > a[stk[top]][j]) top--;
			up[i][j][0] = stk[top]; stk[++top] = i;
		}
		stk[top = 0] = n + 1;
		for (int i = n; i >= 1; i--)
		{
			while (top && a[i][j] > a[stk[top]][j]) top--;
			dn[i][j][0] = stk[top]; stk[++top] = i;
		}
	}
	Log[0] = -1;
	for (int i = 1; i <= m; i++) Log[i] = Log[i >> 1] + 1;
	for (int i = 1; i <= n; i++)
		for (int k = 1; k <= 11; k++)
			for (int j = 1; j + (1 << k) - 1 <= m; j++)
			{
				up[i][j][k] = Max(up[i][j][k - 1], up[i][j + (1 << k - 1)][k - 1]);
				dn[i][j][k] = Min(dn[i][j][k - 1], dn[i][j + (1 << k - 1)][k - 1]);
			}
	solve(2, n - 1);
	return std::cout << ans << std::endl, 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值