【AcWing】蓝桥杯集训每日一题Day17|单调队列|求直方图中最大矩形|单调栈|模型转化|1413.矩形牛棚(C++)

1413.矩形牛棚
1413. 矩形牛棚 - AcWing题库
难度:中等
时/空限制:1s / 256MB
总通过数:1914
总尝试数:3823
来源:

usaco training 6.1
算法标签

单调栈

题目内容

作为一个资本家,农夫约翰希望通过购买更多的奶牛来扩大他的牛奶业务。
因此,他需要找地方建立一个新的牛棚。
约翰购买了一大块土地,这个土地可以看作是一个 R 行(编号 1∼R)C 列(编号 1∼C)的方格矩阵。
不幸的是,他发现其中的部分方格区域已经被破坏了,因此他无法在整个 R×C 的土地上建立牛棚。
经调查,他发现共有 P 个方格内的土地遭到了破坏。
建立的牛棚必须是矩形的,并且内部不能包含被破坏的土地。
请你帮约翰计算,他能建造的最大的牛棚的面积是多少。

输入格式

第一行包含三个整数 R,C,P。
接下来 P 行,每行包含两个整数 r,c,表示第 r 行第 c 列的方格区域内土地是被破坏的。

输出格式

输出牛棚的最大可能面积。

数据范围

1≤R,C≤3000,
0≤P≤30000,
1≤r≤R,
1≤c≤C

输入样例:
3 4 2
1 3
2 1
输出样例:
6
题目解析

行数和列数都是在3000,整个矩阵的大小就是3000的平方,将近1000万的数据量
需要将时间复杂度控制在 O ( n m ) O(nm) O(nm),跟整个矩阵的方格数呈线性关系

枚举

枚举左右边界,再把上下边界枚举出来,再判断中间的格子是不是都没有被破坏
左右边界,n2,上下边界n2,中间的格子数量n^2,时间复杂度是 O ( n 6 ) O(n^6) O(n6)

优化

求中间格子里有没有坏方块,可以用二维前缀和,就不需要枚举了,直接算一下中间的总和是不是0就可以了
如果被破坏的话,就是1;没被破坏,就是0
用二维前缀和可以优化掉一个n^2

考虑能不能枚举少一些的边

可以先用 O ( n ) O(n) O(n)枚举下边界,下边界确定之后,可以预处理一下,每一列都可以求一下往上最多有多少块没有被连续破坏的方块
这样就可以得到一个直方图,求这个直方图中的最大矩形
可以用单调栈,做到 O ( n ) O(n) O(n)的计算量
总共就是 O ( n 2 ) O(n^2) O(n2)

单调栈

O ( n ) O(n) O(n)的时间预处理出来每个数左边第一个比它小的数,以及每个数右边第一个比它小的数

为什么可以用单调栈求解

在此基础上枚举一下上边界,矩形的上边界一定会取直方图的上面的一条边,可以依次枚举一下到底取哪一个小长条的上面的边
当枚举到其中一条边时,上下边界就确定了,接下来考虑左右边界,左边如果能延伸,就一直往左边延伸,知道延伸到左边第一个比当前的高度低的方块为止,有边界也是
![[Pasted image 20240409212850.png]]

上下边界确定之后,左边边界就等于左边第一个比它矮的长条右边这条边,右边边界就是右边第一个比当前矮的长条的左边这一条边
因此只要对于每一个长条,预处理出来左边第一个比它矮的长条,和右边第一个比它矮的长条,就可以知道左右边界了,进而就可以知道矩形的宽度了
就可以知道当前的最大面积是多少

高度预处理,每一列的高度都是独立的,所以可以按列预处理,通过递推的方法

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

using namespace std;

const int N = 3010;

int n, m, P;
int g[N][N], h[N][N];  
//g表示每个位置有没有被破坏,h表示每个格子往上数最多可以数多少个没有被破坏的矩形
int stk[N], top;   //定义一个单调栈
int l[N], r[N];   //存一下左右第一个比它小的数

//确定下边界之后,如果求最大的面积
int work(int h[])
{
	//定义两个哨兵,最左边再画一个高度是-1,右边也是,这样对于每个格子,即使高度是0,左右也会存在一个比它小的,这样不需要特判边界
	h[0] = h[m + 1] = -1;
	top = 0;   //单调栈的栈先初始化为0
	//预处理左边
	stk[++ top] = 0;  //把左边界加进去
	for (int i = 1; i <= m; i ++)
	{
		//栈顶元素大于等于当前元素,就把栈顶元素弹出
		while (h[stk[top]] >= h[i]) top --;
		//左边第一个比当前元素小的元素就是栈顶元素
		l[i] = stk[top];
		//将当前元素加到栈当中
		stk[++ top] = i;
	}
	//同理预处理右边
	top = 0;
	stk[++ top] = m + 1;
	for (int i = m; i; i --)
	{
		while (h[stk[top]] >= h[i]) top --;
		r[i] = stk[top];
		stk[++ top] = i;
	}

	//定义一下答案
	int res = 0;
	//枚举一下最高点
	for (int i = 1; i <= m; i ++)
		res = max(res, h[i] * (r[i] - l[i] - 1));
	return res;
}

int main()
{
	//读入行数列数和被破坏矩形的数量
	scanf("%d%d%d", &n, &m, &P);
	//接下来读入每一个被破坏的位置
	while (P --)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		g[x][y] = 1;   //如果被破坏的话,标记成1
	}

	//预处理h数组
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= m; j ++)
			//如果当前的格子没有被破坏
			if (!g[i][j])
				h[i][j] = h[i - 1][j] + 1;

	//定义答案
	int res = 0;
	//枚举一下下边界
	for (int i = 1; i <= n; i ++)
		res = max(res, work(h[i]));

	printf("%d\n", res);

	return 0;
}
  • 30
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值