POJ 1156 单调队列优化

原题链接

题意

  • 给我们一个n * m矩阵,要求我们求出一个面积最大的子矩阵,满足其内部的极差小于等于c, 同时宽度小于等于100

  • 输入 m, n, c,求这个最大面积。n,m <= 700,c <= 10

思路

  • 如果是暴力枚举的话,每次需要枚举子矩阵,并且扫描一遍这个子矩阵,时间复杂度是无法通过的。

  • 我们考率另一种枚举方式,我们先枚举左边界l与右边界r,这样是100*m的复杂度。

  • 然后我们要考虑的是在这个
    [ l , r ] ∗ [ 1 , n ] [l, r] * [1, n] [l,r][1,n]
    的长条矩阵中拿到一块符合要求的
    [ l , r ] ∗ [ b o t t o m , t o p ] [l, r] * [bottom, top] [l,r][bottom,top]
    然后更新答案

  • 如果能在线性时间内算出这个结果, 那么就是可行的算法。我们首先可以对这个长条状的子矩阵进行预处理,对每一行都预处理出它的最大值和最小值。这个操作看似是 O ( ( r − l + 1 ) ∗ n ) O((r - l + 1) * n) O((rl+1)n) 的,但是因为我们先枚举的左区间之后枚举的右区间,所以本次处理每行的最大最小值只需要将增加的有边界的一列数考虑进去就可以了,时间为 O ( n ) O(n) O(n)

  • 之后这个子问题也就转化成了“一个序列中选出极差小于等于c的最长的一段子序列”。这是有关单向逐步移动的区间最值问题,所以可以使用单调队列,可以满足在线性时间内完成这个任务,求出 [ b o t t o m , t o p ] [bottom, top] [bottom,top]

  • 总时间复杂度为 O(100 * n * m) (可能这也是题目中限定宽度为100的原因)

AC代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int m, n, c;
int aa[705][705];
int maxl[705], minl[705];
int quemx[705], quemn[705];

int main()
{
    while (scanf("%d%d%d", &m, &n, &c) == 3)
	{
		for (int i = 1; i <= n; ++i)
		{
			for (int j = 1; j <= m; ++j)
			{
				scanf("%d", &aa[i][j]);
			}
		}
		int ans = 0;
		for (int l = 1; l <= m; ++l)
		{
			for (int i = 1; i <= n; ++i)
			{
				maxl[i] = aa[i][l];
				minl[i] = aa[i][l];
			}
			int mxr = min(m, l + 99);
			for (int r = l; r <= mxr; ++r)
			{
				for (int i = 1; i <= n; ++i)
				{
					maxl[i] = max(maxl[i], aa[i][r]);
					minl[i] = min(minl[i], aa[i][r]);
				}
				int hx = 1;
				int hn = 1;
				int tx = 1;
				int tn = 1;
				int ll = 1;
				for (int i = 1; i <= n; ++i)
				{
					while (hx != tx && maxl[quemx[tx - 1]] <= maxl[i])
					{
						--tx;
					}
					quemx[tx++] = i;
					while (hn != tn && minl[quemn[tn - 1]] >= minl[i])
					{
						--tn;
					}
					quemn[tn++] = i;

					while (hn != tn && hx != tx && maxl[quemx[hx]] - minl[quemn[hn]] > c)
					{
						++ll;
						if (quemx[hx] < ll)
						{
							++hx;
						}
						if (quemn[hn] < ll)
						{
							++hn;
						}
					}
					ans = max(ans, (i - ll + 1) * (r - l + 1));
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值