[单调栈][动态规划] 最大正方形


题目描述

最大正方形

解题思路(单调栈)

这个题很明显可以用单调栈来解决(重点在DP,这个我就简单说一下),我们先把这个题简化一下,如果这个题只输入一行数列a(表示一排建筑),a[i] 表示一栋建筑的高度,现在我们要在这些建筑中安装一块正方形广告牌,求这个广告牌的最大边长
很明显,如果广告牌挂在 i,i + 1,i + 2……j - 1,j 这几栋建筑上的话,这个广告牌的边长就应该是
min(j - i + 1, min(a[i], a[i + 1], a[i + 1]……a[j - 1], a[j]))
我们令 l[i] 表示广告牌要挂在第 i 栋建筑上时最左边的建筑编号,r[i] 表示广告牌要挂在第 i 栋建筑上时最右边的编号
所以我们就可以将所有建筑一栋一栋的加到栈里面,维护栈的上升单调性(因为我们要找最矮的建筑),如果第 i 栋建筑被第 j 栋建筑弹出(a[j] < a[i]),那么 r[i] = j - 1;反之,如果第 i 栋建筑阻碍了第 j 栋建筑(a[i] < a[j]),那么 l[j] = i + 1
当然,这只是简化题目后的做法,但其实二维的做法也是一样,只需要一排一排的处理就可以了,如果想在优化一点的话,我们可以加入预处理:
p[i][j] 表示第 i 行第 j 列这个点和它上方第一个0之间的距离
那么我们就可以进行预处理了,如果 c[i][j] 为0,p[i][j] 就为0;如果 c[i][j] 为1,p[i][j] 就为 p[i - 1][j] + 1
预处理之后,再逐行求解,然后,完了……

参考代码(单调栈)

#include <cstdio>
#include <cstring>
 
#define reg register
#define M 1005
 
int n, m, ans, Index;
int a[M][M], p[M][M], l[M], r[M], sta[M];
 
inline void read(int &x) {
    x = 0; int f = 1; char s = getchar();
    while(s < '0' || s > '9') {if(s == '-') f = -1; s = getchar();}
    while(s >= '0' && s <= '9') {x = (x << 3) + (x << 1) + s - 48; s = getchar();}
    x *= f;
}
 
inline void wri(int x) {
    if(x < 0) {x = -x; putchar('-');}
    if(x / 10) wri(x / 10);
    putchar(x % 10 + 48);
}
 
inline void write(int x, char s) {
    wri(x);
    putchar(s);
}
 
inline int max(int x, int y) {
    return x > y ? x : y;
}
 
inline int min(int x, int y) {
    return x < y ? x : y;
}
 
int main() {
    read(n), read(m);
    for(reg int i = 1;i <= n;i ++)
        for(reg int j = 1;j <= m;j ++)
            read(a[i][j]);
    for(reg int i = 1;i <= n;i ++)	//预处理
        for(reg int j = 1;j <= m;j ++) {
            if(a[i][j] == 0) p[i][j] = 0;
            else p[i][j] = p[i - 1][j] + 1;
        }
    for(reg int i = 1;i <= n;i ++) {	
        Index = 1;
        for(reg int j = 1;j <= m + 1;j ++) {
            while(Index > 0 && p[i][sta[Index - 1]] > p[i][j]) {	//把第 j 栋建筑放进栈
                r[sta[Index - 1]] = j - 1;
                Index --;
            }
            l[j] = sta[Index - 1] + 1;
            sta[Index ++] = j;
        }
        for(reg int j = 1;j <= m;j ++)	//逐一运算,得出最大的 ans
            ans = max(ans, min(p[i][j], (r[j] - l[j] + 1)));
    }
    write(ans, '\n');
    return 0;
}
 

解题思路(动态规划)

这个题我开始是真的没有看出是DP,我一说单调栈,旁边的某位好像十分震惊:“你居然不会做。”然后像看一个智障一样看我,emmm……
话说后来我才发现,原来书上有DP思路,我就说怎么一个个一把AC,简直震惊!!!
我们步入正题,既然是DP,我们就要有状态的转移:
dp[i][j] 表示以第 i 行第 j 列为右下角的最大正方形的长度
那么 dp[i][j] 可能由哪些状态转移过来呢?
很明显,dp[i - 1][j - 1],dp[i][j - 1] 和 dp[i - 1][j] 都有可能转移到 dp[i][j],现在的问题就是如何去选择这三种状态:
在这里插入图片描述
在上图中,我们可以看到以 (i - 1,j - 1) 这个点为右下角的最大正方形的边长为2(红色),以 (i - 1,j) 这个点为右下角的最大正方形的边长为3(绿色),以 (i,j - 1) 这个点为右下角的正方形的边长为1(蓝色),那么以 (i,j) 为右下角的最大正方形的边长应该为多少呢?
其实很简单,要找最大嘛,取三个中的最大值就可以了呀,于是就有了这样的状态转移方程:
dp[i][j] = max(dp[i][j],max(dp[i - 1][j - 1],dp[i - 1][j],dp[i][j - 1]) + 1)
但我们细细想一下,既然是最大,那么 (i - 1,j) 只能往上和往左延伸3个单位,(i,j - 1) 只能往上和往左延伸1单位,(i - 1,j - 1) 只能往上和往左延伸2个单位,所以我们如果选了最大的,其实只能满足一个方向达到那个最大长度,而另一方向并不能达到这个长度,这就会形成一个长方形,而不是正方形,所以我们就只能在前三种状态中取一个最小的值,即:
dp[i][j] = min(dp[i][j],min(dp[i - 1][j - 1],dp[i - 1][j],dp[i][j - 1]) + 1)
然后就完了……

参考代码(动态规划)

#include <cstdio>
#include <cstring>
 
#define reg register
#define M 1005
 
int n, m, ans;
int a[M][M], dp[M][M];
 
inline void read(int &x) {
    x = 0; int f = 1; char s = getchar();
    while(s < '0' || s > '9') {if(s == '-') f = -1; s = getchar();}
    while(s >= '0' && s <= '9') {x = (x << 3) + (x << 1) + s - 48; s = getchar();}
    x *= f;
}
 
inline void wri(int x) {
    if(x < 0) {x = -x; putchar('-');}
    if(x / 10) wri(x / 10);
    putchar(x % 10 + 48);
}
 
inline void write(int x, char s) {
    wri(x);
    putchar(s);
}
 
inline int max(int x, int y) {
    return x > y ? x : y;
}
 
inline int min(int x, int y) {
    return x < y ? x : y;
}
 
int main() {
    read(n), read(m);
    for(reg int i = 1;i <= n;i ++)
        for(reg int j = 1;j <= m;j ++)
            read(a[i][j]);
    for(reg int i = 1;i <= n;i ++)
        for(reg int j = 1;j <= m;j ++) {
            if(a[i][j] == 1)
                dp[i][j] = max(dp[i][j], min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1);
            ans = max(ans, dp[i][j]);
        }
    write(ans, '\n');
    return 0;
}
 

猜你喜欢

转载自blog.csdn.net/weixin_43896346/article/details/86229626