【2019牛客暑期多校训练营 第二场 H题 Second Large Rectangle】【最大全1子矩阵 -> 次大全1子矩阵】【递推做法和单调栈做法】

题目链接:
https://ac.nowcoder.com/acm/contest/882/H
题意:
给你一个N * M的01矩阵,求次大全1的子矩阵的面积(可以等于最大的子矩阵)。
题解:
要会这题,首先要会求最大的全1子矩阵,可以先去练一下洛谷P4147玉蟾宫
这里介绍两种方法,一种是递推做的,另一种是单调栈做的。
首先是递推做的
l e f t [ i ] [ j ] left[i][j] left[i][j]为点 ( i , j ) (i, j) (i,j)连续的最左边的1位置, r i g h t [ i ] [ j ] right[i][j] right[i][j]为点 ( i , j ) (i, j) (i,j)连续的最右边的1位置, h e i g h t [ i ] [ j ] height[i][j] height[i][j]为点 ( i , j ) (i, j) (i,j)连续的最上边的1位置。
则当前点全1子矩阵面积为 ( r i g h t [ i ] [ j ] − l e f t [ i ] [ j ] + 1 ) ∗ h e i g h t [ i ] [ j ] (right[i][j] - left[i][j] + 1) * height[i][j] (right[i][j]left[i][j]+1)height[i][j]
但是你必须保证从 ( i − h e i g h t [ i ] [ j ] + 1 , j ) (i - height[i][j] + 1, j) (iheight[i][j]+1,j) ( i , j ) (i, j) (i,j)为中心的所有的全1线段都相同,
即保证 l = m a x ( l , l e f t [ k ] [ j ] ) , r = m i n ( r , r i g h t [ k ] [ j ] ) l = max(l, left[k][j]), r = min(r, right[k][j]) l=max(l,left[k][j]),r=min(r,right[k][j]),其中 k ∈ [ j − h e i g h t [ i ] [ j ] + 1 , j ] k∈[j - height[i][j] + 1, j] k[jheight[i][j]+1,j],也就是这样:
在这里插入图片描述
因为最终是自上而下遍历的,所以递推式为
l e f t [ i ] [ j ] = m a x ( l e f t [ i ] [ j ] , l e f t [ i − 1 ] [ j ] ) left[i][j] = max(left[i][j], left[i - 1][j]) left[i][j]=max(left[i][j],left[i1][j])
r i g h t [ i ] [ j ] = m i n ( r i g h t [ i ] [ j ] , r i g h t [ i − 1 ] [ j ] ) right[i][j] = min(right[i][j], right[i - 1][j]) right[i][j]=min(right[i][j],right[i1][j])
这样就可以求出面积S = ( r i g h t [ i ] [ j ] − l e f t [ i ] [ j ] + 1 ) ∗ h e i g h t [ i ] [ j ] (right[i][j] - left[i][j] + 1) * height[i][j] (right[i][j]left[i][j]+1)height[i][j], 再与当前最大比较更新即可
在上图中,可以发现,在第 i i i行,在左边界点和右边界点之间的点求出来的最大子矩阵实际上是同一个,所以这里求次大的子矩阵不能单纯的比较完面积后就更新(有可能求出来的都是同一个子矩阵),而是要记录上一个最大子矩阵的左边界点,右边界点,上边界点,只要有一个数值和当前这个不同,那就说明不是同一个子矩阵,以此更新次大子矩阵。
递推的代码:

const int MAX = 1e3 + 10;

int N, M;
int grap[MAX];
int l[2][MAX], r[2][MAX], h[2][MAX];
//因为只用到了当前行和上一行,所以可以用滚动数组优化空间
char str[MAX];

int main() {
	int maxx = 0, maxx2 = 0, max_l, max_r, max_h;//最大值和次大值
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= N; i++) {
		scanf("%s", str + 1);
		int now = i % 2, pre = now ^ 1;
		for (int j = 1; j <= M; j++) {
			if (str[j] == '1')grap[j] = 1, h[now][j] = h[pre][j] + 1;
			else grap[j] = 0, h[now][j] = 0;
			l[now][j] = r[now][j] = j;
		}

		//左边界
		for (int j = 2; j <= M; j++)
			if (grap[j] == 1 && grap[j - 1] == 1)
				l[now][j] = l[now][j - 1];
		//右边界
		for (int j = M - 1; j >= 1; j--)
			if (grap[j] == 1 && grap[j + 1] == 1)
				r[now][j] = r[now][j + 1];

		for (int j = 1; j <= M; j++) {
			if (grap[j] != 1)continue;
			if (i > 1 && h[pre][j]) {
				l[now][j] = max(l[now][j], l[pre][j]);
				r[now][j] = min(r[now][j], r[pre][j]);
			}
			int t = (r[now][j] - l[now][j] + 1) * h[now][j];//当前面积
			if (t > maxx) {
				maxx2 = maxx, maxx = t;
				max_l = l[now][j], max_r = r[now][j], max_h = h[now][j];
			}
			else if (max_l != l[now][j] || max_r != r[now][j] || max_h != h[now][j])//如果不是原来那个子矩阵
				maxx2 = max(maxx2, t);
		}
	}
	//次大值还有可能是最大矩阵减一行或者减一列
	maxx2 = max(maxx2, (max_r - max_l) * max_h);
	maxx2 = max(maxx2, (max_r - max_l + 1) * (max_h - 1));
	printf("%d\n", maxx2);
	return 0;
}

第二种是单调栈做的。
可以看到递推的做法是在一列上进行操作的,而单调栈的做法是在一行上进行操作的。
在第 i i i行上建立一个自底到顶单调递增的栈,栈中记录每个点的上边界 h [ j ] h[j] h[j]和位置 j j j
如果 h [ j ] &gt; = s t [ t o p ] . h h[j] &gt;= st[top].h h[j]>=st[top].h,即当前点的上边界大于等于栈顶的上边界,那么入栈
如果 h [ j ] &lt; s t [ t o p ] . h h[j] &lt; st[top].h h[j]<st[top].h,那么就出栈,直到 h [ j ] &gt; = s t [ t o p ] . h h[j] &gt;= st[top].h h[j]>=st[top].h,每出栈一个元素就计算一遍面积
因为堆栈是递增的,所以从栈顶的位置直到当前位置前面一位,也就是 s t [ t o p ] . p st[top].p st[top].p j − 1 j - 1 j1上面最小高度就是 s t [ t o p ] . h st[top].h st[top].h
所以有面积计算公式 S = ( j − s t [ t o p ] . p ) ∗ s t [ t o p ] . h S = (j - st[top].p) * st[top].h S=(jst[top].p)st[top].h
在这里插入图片描述
代码:

const int MAX = 1e3 + 10;

struct node {
	int h, p;
}st[MAX];

int N, M;
int h[MAX];
char str[MAX];

int main() {
	int maxx = 0, maxx2 = 0;
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= N; i++) {
		scanf("%s", str + 1);
		for (int j = 1; j <= M; j++) {
			if (str[j] == '1')h[j] = h[j] + 1;
			else h[j] = 0;
		}
		int top = 0;
		h[M + 1] = -1;//在末尾后面在加一个最小的元素,使得[1,M]上得到的单调栈全部出栈并计算面积
		st[top].h = -1;
		st[top].p = 0;
		for (int j = 1; j <= M + 1; j++)
			if (h[j] >= st[top].h) {//入栈
				st[++top].h = h[j];
				st[top].p = j;
			}
			else {
				while (h[j] < st[top].h) {//出栈并计算面积
					int t = (j - st[top].p) * st[top].h;
					if (t >= maxx)maxx2 = maxx, maxx = t;
					else maxx2 = max(maxx2, t);
					maxx2 = max(maxx2, (j - st[top].p - 1) * st[top].h);
					maxx2 = max(maxx2, (j - st[top].p) * (st[top].h - 1));
					top--;
				}
				st[++top].h = h[j];
			}
	}
	printf("%d\n", maxx2);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值