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)的时间预处理出来每个数左边第一个比它小的数,以及每个数右边第一个比它小的数
为什么可以用单调栈求解
在此基础上枚举一下上边界,矩形的上边界一定会取直方图的上面的一条边,可以依次枚举一下到底取哪一个小长条的上面的边
当枚举到其中一条边时,上下边界就确定了,接下来考虑左右边界,左边如果能延伸,就一直往左边延伸,知道延伸到左边第一个比当前的高度低的方块为止,有边界也是
上下边界确定之后,左边边界就等于左边第一个比它矮的长条右边这条边,右边边界就是右边第一个比当前矮的长条的左边这一条边
因此只要对于每一个长条,预处理出来左边第一个比它矮的长条,和右边第一个比它矮的长条,就可以知道左右边界了,进而就可以知道矩形的宽度了
就可以知道当前的最大面积是多少
高度预处理,每一列的高度都是独立的,所以可以按列预处理,通过递推的方法
代码
#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;
}