题目链接:
https://ac.nowcoder.com/acm/contest/882/H
题意:
给你一个N * M的01矩阵,求次大全1的子矩阵的面积(可以等于最大的子矩阵)。
题解:
要会这题,首先要会求最大的全1子矩阵,可以先去练一下洛谷P4147玉蟾宫。
这里介绍两种方法,一种是递推做的,另一种是单调栈做的。
首先是递推做的
记
l
e
f
t
[
i
]
[
j
]
left[i][j]
left[i][j]为点
(
i
,
j
)
(i, j)
(i,j)连续的最左边的1位置,
r
i
g
h
t
[
i
]
[
j
]
right[i][j]
right[i][j]为点
(
i
,
j
)
(i, j)
(i,j)连续的最右边的1位置,
h
e
i
g
h
t
[
i
]
[
j
]
height[i][j]
height[i][j]为点
(
i
,
j
)
(i, j)
(i,j)连续的最上边的1位置。
则当前点全1子矩阵面积为
(
r
i
g
h
t
[
i
]
[
j
]
−
l
e
f
t
[
i
]
[
j
]
+
1
)
∗
h
e
i
g
h
t
[
i
]
[
j
]
(right[i][j] - left[i][j] + 1) * height[i][j]
(right[i][j]−left[i][j]+1)∗height[i][j],
但是你必须保证从
(
i
−
h
e
i
g
h
t
[
i
]
[
j
]
+
1
,
j
)
(i - height[i][j] + 1, j)
(i−height[i][j]+1,j)到
(
i
,
j
)
(i, j)
(i,j)为中心的所有的全1线段都相同,
即保证
l
=
m
a
x
(
l
,
l
e
f
t
[
k
]
[
j
]
)
,
r
=
m
i
n
(
r
,
r
i
g
h
t
[
k
]
[
j
]
)
l = max(l, left[k][j]), r = min(r, right[k][j])
l=max(l,left[k][j]),r=min(r,right[k][j]),其中
k
∈
[
j
−
h
e
i
g
h
t
[
i
]
[
j
]
+
1
,
j
]
k∈[j - height[i][j] + 1, j]
k∈[j−height[i][j]+1,j],也就是这样:
因为最终是自上而下遍历的,所以递推式为
l
e
f
t
[
i
]
[
j
]
=
m
a
x
(
l
e
f
t
[
i
]
[
j
]
,
l
e
f
t
[
i
−
1
]
[
j
]
)
left[i][j] = max(left[i][j], left[i - 1][j])
left[i][j]=max(left[i][j],left[i−1][j])
r
i
g
h
t
[
i
]
[
j
]
=
m
i
n
(
r
i
g
h
t
[
i
]
[
j
]
,
r
i
g
h
t
[
i
−
1
]
[
j
]
)
right[i][j] = min(right[i][j], right[i - 1][j])
right[i][j]=min(right[i][j],right[i−1][j])
这样就可以求出面积S =
(
r
i
g
h
t
[
i
]
[
j
]
−
l
e
f
t
[
i
]
[
j
]
+
1
)
∗
h
e
i
g
h
t
[
i
]
[
j
]
(right[i][j] - left[i][j] + 1) * height[i][j]
(right[i][j]−left[i][j]+1)∗height[i][j], 再与当前最大比较更新即可
在上图中,可以发现,在第
i
i
i行,在左边界点和右边界点之间的点求出来的最大子矩阵实际上是同一个,所以这里求次大的子矩阵不能单纯的比较完面积后就更新(有可能求出来的都是同一个子矩阵),而是要记录上一个最大子矩阵的左边界点,右边界点,上边界点,只要有一个数值和当前这个不同,那就说明不是同一个子矩阵,以此更新次大子矩阵。
递推的代码:
const int MAX = 1e3 + 10;
int N, M;
int grap[MAX];
int l[2][MAX], r[2][MAX], h[2][MAX];
//因为只用到了当前行和上一行,所以可以用滚动数组优化空间
char str[MAX];
int main() {
int maxx = 0, maxx2 = 0, max_l, max_r, max_h;//最大值和次大值
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; i++) {
scanf("%s", str + 1);
int now = i % 2, pre = now ^ 1;
for (int j = 1; j <= M; j++) {
if (str[j] == '1')grap[j] = 1, h[now][j] = h[pre][j] + 1;
else grap[j] = 0, h[now][j] = 0;
l[now][j] = r[now][j] = j;
}
//左边界
for (int j = 2; j <= M; j++)
if (grap[j] == 1 && grap[j - 1] == 1)
l[now][j] = l[now][j - 1];
//右边界
for (int j = M - 1; j >= 1; j--)
if (grap[j] == 1 && grap[j + 1] == 1)
r[now][j] = r[now][j + 1];
for (int j = 1; j <= M; j++) {
if (grap[j] != 1)continue;
if (i > 1 && h[pre][j]) {
l[now][j] = max(l[now][j], l[pre][j]);
r[now][j] = min(r[now][j], r[pre][j]);
}
int t = (r[now][j] - l[now][j] + 1) * h[now][j];//当前面积
if (t > maxx) {
maxx2 = maxx, maxx = t;
max_l = l[now][j], max_r = r[now][j], max_h = h[now][j];
}
else if (max_l != l[now][j] || max_r != r[now][j] || max_h != h[now][j])//如果不是原来那个子矩阵
maxx2 = max(maxx2, t);
}
}
//次大值还有可能是最大矩阵减一行或者减一列
maxx2 = max(maxx2, (max_r - max_l) * max_h);
maxx2 = max(maxx2, (max_r - max_l + 1) * (max_h - 1));
printf("%d\n", maxx2);
return 0;
}
第二种是单调栈做的。
可以看到递推的做法是在一列上进行操作的,而单调栈的做法是在一行上进行操作的。
在第
i
i
i行上建立一个自底到顶单调递增的栈,栈中记录每个点的上边界
h
[
j
]
h[j]
h[j]和位置
j
j
j,
如果
h
[
j
]
>
=
s
t
[
t
o
p
]
.
h
h[j] >= st[top].h
h[j]>=st[top].h,即当前点的上边界大于等于栈顶的上边界,那么入栈
如果
h
[
j
]
<
s
t
[
t
o
p
]
.
h
h[j] < st[top].h
h[j]<st[top].h,那么就出栈,直到
h
[
j
]
>
=
s
t
[
t
o
p
]
.
h
h[j] >= st[top].h
h[j]>=st[top].h,每出栈一个元素就计算一遍面积
因为堆栈是递增的,所以从栈顶的位置直到当前位置前面一位,也就是
s
t
[
t
o
p
]
.
p
st[top].p
st[top].p到
j
−
1
j - 1
j−1上面最小高度就是
s
t
[
t
o
p
]
.
h
st[top].h
st[top].h
所以有面积计算公式
S
=
(
j
−
s
t
[
t
o
p
]
.
p
)
∗
s
t
[
t
o
p
]
.
h
S = (j - st[top].p) * st[top].h
S=(j−st[top].p)∗st[top].h
代码:
const int MAX = 1e3 + 10;
struct node {
int h, p;
}st[MAX];
int N, M;
int h[MAX];
char str[MAX];
int main() {
int maxx = 0, maxx2 = 0;
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; i++) {
scanf("%s", str + 1);
for (int j = 1; j <= M; j++) {
if (str[j] == '1')h[j] = h[j] + 1;
else h[j] = 0;
}
int top = 0;
h[M + 1] = -1;//在末尾后面在加一个最小的元素,使得[1,M]上得到的单调栈全部出栈并计算面积
st[top].h = -1;
st[top].p = 0;
for (int j = 1; j <= M + 1; j++)
if (h[j] >= st[top].h) {//入栈
st[++top].h = h[j];
st[top].p = j;
}
else {
while (h[j] < st[top].h) {//出栈并计算面积
int t = (j - st[top].p) * st[top].h;
if (t >= maxx)maxx2 = maxx, maxx = t;
else maxx2 = max(maxx2, t);
maxx2 = max(maxx2, (j - st[top].p - 1) * st[top].h);
maxx2 = max(maxx2, (j - st[top].p) * (st[top].h - 1));
top--;
}
st[++top].h = h[j];
}
}
printf("%d\n", maxx2);
return 0;
}