HDU Maximal submatrix(单调栈/悬线法)

目录

单调栈

悬线法


题目链接:Problem - 6957 (hdu.edu.cn)

题意:输入一个n行m列的矩阵,找到每一列非递减的最大子矩形面积。

知识点

方法一:单调栈

1.单调栈定义:

(1)单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小

(2)单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大

2.单调栈解法:

(1)首先预处理出自该位置向上满足性质的数的个数,记为 h[i][j]。

(2)对于每行 (h[i]) 维护一个单调递增栈,所有元素进栈和出栈一次,每个元素出栈时更新最大的矩形面积。

(3)为了能够更新最大的矩形面积,我们需要在栈内记录两个数据——元素的高度 h[i][j] 和它到栈中上一个元素的宽度差。

(4)元素出栈时,因为高度逐渐递减,故记width 为当前已经弹出的元素的宽度和。

3.常见应用:柱状图中的最大面积/离线RMQ问题/优化DP

方法二:悬线法

1.适用范围:悬线法的适用范围是单调栈的子集。具体为:

  • 需要在扫描序列时维护单调的信息;
  • 可以使用单调栈解决;
  • 不需要在单调栈上二分。

2.用于解决的问题有:最大子矩阵问题

例如:在一条水平线上有 n个宽为1 的矩形,求包含于这些矩形的最大子矩形面积。

3.具体方法

参考:悬线法 - OI Wiki (oi-wiki.org)

       悬线,就是一条竖线,这条竖线有初始位置高度两个性质,可以在其上端点不超过当前位置的矩形高度的情况下左右移动。

       对于一条悬线,我们在这条上端点不超过当前位置的矩形高度且不移出边界的前提下,将这条悬线左右移动,求出其最多能向左和向右扩展到何处,此时这条悬线扫过的面积就是包含这条悬线的尽可能大的矩形。容易发现,最大子矩形必定是包含一条初始位置为 i,高度为 hi 的悬线。枚举实现这个过程的时间复杂度为O(n^2) ,但是我们可以用悬线法将其优化到 O(n)。

        定义li为当前找到的i位置的悬线能扩展到的最左边的位置,容易得到li初始为i,我们需要进一步判断还能不能进一步往左扩展。

  • 如果当前 li=1,则已经扩展到了边界,不可以。
  • 如果当前 hi>hli-1,则从当前悬线扩展到的位置不能再往左扩展了。
  • 如果当前 hi<=hli-1 ,则从当前悬线还可以往左扩展,并且li-1位置的悬线能向左扩展到的位置,i位置的悬线一定也可以扩展到,于是我们将 li 更新为 lli-1 ,并继续执行判断

       对于ri也同理。

悬线法求最大矩形代码:

#include<iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
#define  N 100010//最大矩形宽度
int n;//矩形个数
int h[N];//每个矩形高度
int l[N], r[N];//i位置的悬线能扩展到的最左边的位置和最右边的位置
long long ans;
int main() 
{
	cin >> n;
    ans = 0;
	for (int i = 1; i <= n; i++)
	{
		cin >> h[i];
		l[i] = r[i] = i;
	}
		for (int i = 1; i <= n; i++)
			while (l[i] > 1 && h[i] <= h[l[i] - 1]) 
				l[i] = l[l[i] - 1];
		for (int i = n; i >= 1; i--)
			while (r[i] < n && h[i] <= h[r[i] + 1]) 
				r[i] = r[r[i] + 1];
		for (int i = 1; i <= n; i++)
			ans = max(ans, (long long)(r[i] - l[i] + 1) * h[i]);//最左位置到最右位置乘上本身高度即为矩形面积
		printf("%lld\n", ans);
    return 0;
}

本题代码:

1.预处理出自该位置向上满足性质的数的个数,记为 h[i][j]。

2.用悬线法求最大矩形。

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define maxn 2005
int v[maxn][maxn];
int h[maxn][maxn];
int l[maxn][maxn];//i行j列的悬线最左到达位置
int r[maxn][maxn];//i行j列的悬线最右到达位置
void solve()
{
	int n, m;
	int ans = 0;
//记录自该位置向上满足性质的数字
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
		{
			scanf_s("%d",&v[i][j]);
			h[i][j] = 1;
			l[i][j] = r[i][j] = j;//初始化
		}
	for(int j=1;j<=m;j++)
		for (int i = 1; i < n; i++)
		{
			if (v[i][j] <= v[i + 1][j])
				h[i + 1][j] = h[i][j] + 1;
		}
	//求左位置:从左开始求
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
		{
			while (l[i][j] > 1 && h[i][j] <= h[i][l[i][j] - 1])
				l[i][j] = l[i][l[i][j] - 1];		
		}
	//求右位置:从右开始求
	for(int i=1;i<=n;i++)
		for (int j = m; j >= 1; j--)
		{
			while (r[i][j] < m && h[i][j] <= h[i][r[i][j] + 1])
				r[i][j] = r[i][r[i][j] + 1];
		}
	//求最大矩形
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			ans = max(ans, (r[i][j] - l[i][j] + 1) * h[i][j]);
		}
	}
	cout << ans << endl;
}
int main()
{
	int T;
	cin >> T;
	while (T--)
	{
		solve();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值