2019牛客暑期多校训练营(第二场)Second Large Rectangle (单调栈)

单调栈讲解:

已经会单调栈的可以直接跳过看 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);
}


 


 

 

 
 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值