单调栈讲解:
已经会单调栈的可以直接跳过看 2019牛客暑期多校训练营(第二场)Second Large Rectangle
单调栈:
先来说下单调栈,单调栈按字面意思就是栈,但是里面的值是单调的。举个例子:
给你一组序列 : 2 1 4 5 1 3 3 让这组序列进单调栈,步骤:
1. st : 2
2. st : 1 (因为 1 比 2 为了保证单调递增,所以把 2 先出栈,再把 1 压入栈中)
3. st : 1 4
4. st : 1 4 5
5. st : 1
6. st : 1 3
7. st : 1 3
最终: 2 1 4 5 1 3 3 进单调递增栈之后结果为 : st : [ 1 3 ]
单调栈说完了,就这么简单,那么下面说下他的应用(当然重要的是应用)。
单调栈的应用:求最大矩形
先来看一道经典的题目:Largest Rectangle in a Histogram https://vjudge.net/problem/HDU-1506
给你n个数,每个数 x 代表一个底为 1 高为 x 的矩形,如: 2 1 4 5 1 3 3
2 1 4 5 1 3 3
然后问你这个图形里包含的最大矩形的面积?
对于这个题就是下图中阴影部分的面积:
那么这道题怎么解决呢 ? 就可以利用单调栈,并且时间复杂度是 O(n) 的。
这么来想这问题: (下面的 “小矩形” 就是代表 底为1的矩形)
1. 首先,这个最终的最大矩形的 高 肯定是这 n 个小矩形中 其中一个的 高 。
2. 那么,就可以枚举每一个小矩形的高,然后只要左边或者右边的小矩形的高大于等于该小矩形的高,那么就进行延伸。
然后可以暴力去求解,直接对于每一个小矩形往两边扩展,时间复杂度为 O(n^2) 的。 那么大体的思路知道了,剩下的就是利用 单调栈 进行优化,将该思想的实现变为 O(n) 的复杂度。
首先,先按从左到右的顺序压入单调栈,并记录一个 L[ i ] 数组,该数组用保存第 i 个小矩形的往左扩展的最远的下标。
举个例子: 那之前的 2 1 4 5 1 3 3 这个序列模拟一下。 初始化单调栈 st : [ ]
1. st : [ 2 ] // 2进来单调栈了,说明当前 2 位置之前的数都小于 2 不能再向左扩展了,记录 2 可以扩展到最左边的位置 L[ 1 ] = 1 .
2. st : [ 1 ] // 1进来单调栈了,说明当前 1 位置之前的数都小于 1 不能再向左扩展了,记录 1 可以扩展到最左边的位置 L[ 2 ] = 1 .
3. st : [ 1 4 ]
// 4进来单调栈了,说明当前 4 位置之前的数都小于 4 不能再向左扩展了,记录 4 可以扩展到最左边的位置 , 这个位置怎么计算呢 ? 应该是单调栈中与 4 相邻的左边的 下标 + 1 ,就是 : L[ 3 ] = 2 + 1 = 3 ( 1 在原数组中的下标为 2 )
4. ... 依次来求
最终的结果为 :
i | 1 2 3 4 5 6 7
h | 2 1 4 5 1 3 3
L | 1 1 3 4 1 6 6
R | 1 7 4 4 7 7 7
S | 2 7 8 5 7 6 6 // S = ( R - L + 1) * h
AC代码 + 单调栈模板:
#include<bits/stdc++.h>
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug prllf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false),cin.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll maxn = 1e5 + 7;
const double pi = acos(-1);
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;
ll n, h[maxn], L[maxn], x, R[maxn];
ll st[maxn] ; // 为了方便使用栈保存下标,而不是保存高
int main()
{
while(~scanf("%lld", &n) && n)
{
for(int i = 1; i <= n; i++)
{
scanf("%lld", &h[i]);
}
// 单调栈模板
/*********************** 单调栈快速求解 L[i] R[i] ************************/
ll t = 0; // 模拟栈顶指针
for(int i = 1; i <= n; i++)
{
while(t > 0 && h[ st[t - 1] ] >= h[i] ) t--; // 比当前的大就出栈(使用t--模拟)
L[i] = t == 0 ? 1 : st[t - 1] + 1 ;
st[t++] = i;
}
t = 0;
for(int i = n; i >= 1; i--)
{
while(t > 0 && h[ st[t - 1] ] >= h[i] ) t--;
R[i] = t == 0 ? n : st[t - 1] - 1;
st[t++] = i;
}
/*****************************************************************/
ll ans = 0;
for(ll i = 1; i <= n; i++)
{
ans = max(ans, (R[i] - L[i] + 1) * h[i]);
}
printf("%lld\n", ans);
}
}
2019牛客暑期多校训练营(第二场)Second Large Rectangle :
题意:
给你一个n*m的01矩阵,问你该矩阵中由1构成的第二大的矩阵的大小为多少?
解题思路:
首先预处理出来每个 1 的坐标上有多少个连续的 1 ,然后对于每一行用单调栈求解 矩形面积即可,把所有矩形的面积求出来,然后取第二大。
注意:
1. 矩形不要算重复了,可以用 map 去重,根据每一行的 L[i] R[i] 来判断,不可以根据连续的高相同来判断(反例 :1 2 1 这时 两边的 1 其实构成的矩形是一个 只可以算一个 )
2. 一个矩形内部也可以形成一个新的矩形。
有不理解的看下代码应该就清楚了。
AC代码:
#include<bits/stdc++.h>
#define up(i, x, y) for(int i = x; i <= y; i++)
#define down(i, x, y) for(int i = x; i >= y; i--)
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false),cin.tie(0)
typedef long long ll;
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int maxn = 1e3 + 7;
const double pi = acos(-1);
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;
int maxx = -1, smaxx = -1;
int n, L[maxn], R[maxn], st[maxn];
int h[maxn][maxn];
void fun(int area)
{
if(area <= 0) return ;
if(area > maxx)
{
smaxx = maxx;
maxx = area;
}
else if(area > smaxx)
{
smaxx = area;
}
}
struct node
{
int l, r, h, id;
};
vector<node> vec;
node xx;
int main()
{
int n, m; scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++)
{
for(int j = 0; j < m; j++)
{
scanf("%1d", &h[i][j]);
h[i][j] *= (1 + h[i - 1][j]);
}
}
for(int i = 1; i <= n; i++)
{
int t = 0;
map<int , map<int ,int> > mp;
for(int j = 0; j < m; j++)
{
while(t > 0 && h[i][ st[t - 1] ] >= h[i][j] ) t--;
L[j] = t == 0 ? 0 : (st[t - 1] + 1);
st[t++] = j;
}
t = 0;
for(int j = m - 1; j >= 0; j--)
{
while(t > 0 && h[i][ st[t - 1] ] >= h[i][j]) t--;
R[j] = t == 0 ? m : st[t - 1];
st[t++] = j;
}
for(int j = 0; j < m; j++)
{
xx.id = i;
xx.l = L[j];
xx.r = R[j];
xx.h = h[i][j];
if(mp[ L[j] ][ R[j] ] == 0) // 1. 去重
{
vec.push_back(xx);
mp[ L[j] ][ R[j] ] = 1;
}
}
}
int len = vec.size();
int area = 0;
for(int i = 0; i < len; i++)
{
xx = vec[i];
area = (xx.r - xx.l) * xx.h; fun(area);
area = (xx.r - xx.l - 1) * xx.h; fun(area); //2. 大矩形里可能存在矩形的处理办法
area = (xx.r - xx.l) * (xx.h - 1); fun(area);
}
printf("%d\n", smaxx == -1 ? 0 : smaxx);
}
//3 6
//010111
//110111
//011100
另一种暴力的做法,预处理下高直接枚举,理论过不了,但可能因为样例比较水,感兴趣的可以看下,只给AC代码:
#include<bits/stdc++.h>
#define up(i, x, y) for(int i = x; i <= y; i++)
#define down(i, x, y) for(int i = x; i >= y; i--)
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false),cin.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int maxn = 1007;
const double pi = acos(-1);
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;
int maxarea = -1, second = 0;
int G[maxn][maxn];
int main()
{
int n, m; scanf("%d %d", &n, &m);
memset(G, 0, sizeof(G));
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
scanf("%1d", &G[i][j]);
G[i][j] *= (1+ G[i - 1][j]);
}
}
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
int height = G[i][j];
int width = 1;
while(height)
{
int area = height * width;
if(area > maxarea)
{
second = maxarea;
maxarea = area;
}
else if(area > second)
{
second = area;
}
height = min(height, G[i][j - width]);
width++;
}
}
}
if(second <= 0) printf("0\n");
else printf("%d\n", second);
}