在动态规划问题中,有一类问题要求大矩阵的最大或最小子矩阵,这类问题往往可以采用悬线法,下面由一道例题来引入此法。
洛谷P1169[ZJOI2007]棋盘制作
1 怎么构造悬线
如果直接暴力枚举此题,时间复杂度会较大,一般情况下都会想到利用DP解题,但难以入手,我们不妨分析下面一个样例。
0
0
1
1
0
0
1
0
0
1
1
1
1
1
1
0
1
1
0
0
\begin{matrix} 0&0&1&1&0& \\0&1&0&0&1\\1&1&1&1&1\\0&1&1&0&0 \end{matrix}
00100111101110100110
在初始状态下,每一个元素都是一根悬线,假设其位置为
m
a
t
r
i
x
[
i
]
[
j
]
matrix[i][j]
matrix[i][j](第
i
i
i行第
j
j
j列),
它具有三个属性,悬线的高度
h
e
i
g
h
t
[
i
]
[
j
]
height[i][j]
height[i][j],悬线向左能到达的最远的合法列
l
e
f
t
[
i
]
[
j
]
left[i][j]
left[i][j]以及悬线向右能达到的最远的合法列
r
i
g
h
t
[
i
]
[
j
]
right[i][j]
right[i][j],所以在初始条件下
h
e
i
g
h
t
[
i
]
[
j
]
=
1
,
l
e
f
t
[
i
]
[
j
]
=
r
i
g
h
t
[
i
]
[
j
]
=
j
height[i][j]=1,left[i][j]=right[i][j]=j
height[i][j]=1,left[i][j]=right[i][j]=j。
不难发现,有如下状态转移方程,固定一行时(因为目前悬线的长度为
1
1
1),从左向右枚举如果
(
i
,
j
)
和
(
i
,
j
−
1
)
(i,j)和(i,j-1)
(i,j)和(i,j−1)两个点互相合法(在此例中是不相等),
l
e
f
t
[
i
]
[
j
]
=
l
e
f
t
[
i
]
[
j
−
1
]
left[i][j]=left[i][j-1]
left[i][j]=left[i][j−1] ,同理从右向左枚举时,如果
(
i
,
j
)
和
(
i
,
j
−
1
)
(i,j)和(i,j-1)
(i,j)和(i,j−1)两个点互相合法,则有
r
i
g
h
t
[
i
]
[
j
]
=
r
i
g
h
t
[
i
]
[
j
+
1
]
right[i][j]=right[i][j+1]
right[i][j]=right[i][j+1],至此,所有高度为
1
1
1的悬线构造完毕。
2 怎么“拼接”悬线
仍使用如上案例
0
0
1
1
0
0
1
0
0
1
1
1
1
1
1
0
1
1
0
0
\begin{matrix} 0&0&1&1&0& \\0&1&0&0&1\\1&1&1&1&1\\0&1&1&0&0 \end{matrix}
00100111101110100110
拼接悬线时我们应该纵向观察,譬如本例中第三列,
l
e
f
t
[
1
]
[
3
]
=
2
,
r
i
g
h
t
[
1
]
[
3
]
=
3
;
l
e
f
t
[
2
]
[
3
]
=
1
,
r
i
g
h
t
[
2
]
[
3
]
=
3
;
l
e
f
t
[
3
]
[
3
]
=
r
i
g
h
t
[
3
]
[
3
]
=
3
left[1][3]=2,right[1][3]=3;left[2][3]=1,right[2][3]=3;left[3][3]=right[3][3]=3
left[1][3]=2,right[1][3]=3;left[2][3]=1,right[2][3]=3;left[3][3]=right[3][3]=3当我们拼接
(
1
,
3
)
,
(
2
,
3
)
(1,3),(2,3)
(1,3),(2,3)两根悬线时,首先要求上下两个元素相互合法,应该取这两根悬线左右移动时都能达到的列,并且我们更新
l
e
f
t
[
2
]
[
3
]
=
m
a
x
(
l
e
f
t
[
2
]
[
3
]
,
l
e
f
t
[
1
]
[
3
]
)
=
2
left[2][3]=max(left[2][3],left[1][3])=2
left[2][3]=max(left[2][3],left[1][3])=2,
r
i
g
h
t
[
2
]
[
3
]
=
m
i
n
(
r
i
g
h
t
[
1
]
[
3
]
,
r
i
g
h
t
[
2
]
[
3
]
)
=
3
right[2][3]=min(right[1][3],right[2][3])=3
right[2][3]=min(right[1][3],right[2][3])=3。当我们更新了
l
e
f
t
[
2
]
[
3
]
和
r
i
g
h
t
[
2
]
[
3
]
left[2][3]和right[2][3]
left[2][3]和right[2][3]之后,可以继续更新
l
e
f
t
[
3
]
[
3
]
left[3][3]
left[3][3]
r
i
g
h
t
[
3
]
[
3
]
right [3][3]
right[3][3]
h
e
i
g
h
t
[
3
]
[
3
]
height[3][3]
height[3][3]。如此一步一步转移,正是一种DP的做法。
可以发现,悬线的定义已经被改变:从这个点开始向上达到的最远合法点的一根悬线。
最后我们找到的最大的合法子矩阵就是
m
a
x
(
(
r
i
g
h
t
[
i
]
[
j
]
−
l
e
f
t
[
i
]
[
j
]
+
1
)
∗
h
e
i
g
h
t
[
i
]
[
j
]
max((right[i][j]-left[i][j]+1)*height[i][j]
max((right[i][j]−left[i][j]+1)∗height[i][j].
3 正确性的证明
拼接悬线后,可能会有所疑惑,我们每次悬线都向上找到了最远合法点,如果是中间有一段悬线达到的最大合法矩阵我们不就找不到了?
看如下示例
1
1
1
1
1
1
1
1
1
1
0
1
0
1
0
1
1
1
1
1
1
1
1
1
1
\begin{matrix} 1&1&1&1&1\\1&1&1&1&1\\0&1&0&1&0\\1&1&1&1&1\\1&1&1&1&1 \end{matrix}
1101111111110111111111011
当我们从第三列去拼接时,显然
l
e
f
t
[
3
]
[
3
]
=
r
i
g
h
t
[
3
]
[
3
]
=
3
left[3][3]=right[3][3]=3
left[3][3]=right[3][3]=3
h
e
i
g
h
t
[
3
]
[
3
]
=
2
height[3][3]=2
height[3][3]=2,但是我们从第四列去拼接时(或者其他列),由于
(
4
,
3
)
和
(
4
,
2
)
(4,3)和(4,2)
(4,3)和(4,2)并不互相合法,所以
(
4
,
3
)
(4,3)
(4,3)仍然是一个高度为
1
1
1的悬线,可以将最大矩形枚举出来。如此一来,枚举所有合法矩形,我们可以得到最终答案,不难看出这个算法的时间复杂度是
O
(
n
m
)
O(nm)
O(nm)。
4例题解答
代码如下
#include<bits/stdc++.h>
using namespace std;
int matrix[2005][2005],l[2005][2005],r[2005][2005],height[2005][2005];
int max1=0;
int max2=0;
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>matrix[i][j];
l[i][j]=j;
r[i][j]=j;
height[i][j]=1;//初始化一根悬线
}
}
for(int i=1;i<=n;i++){
for(int j=2;j<=m;j++){//从左向右扫描,更新left[i][j]
if(matrix[i][j]!=matrix[i][j-1]){
l[i][j]=l[i][j-1];
}
}
}
for(int i=1;i<=n;i++){
for(int j=m-1;j>=1;j–){
if(matrix[i][j]!=matrix[i][j+1]){
r[i][j]=r[i][j+1];
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i>1&&matrix[i][j]!=matrix[i-1][j]){
l[i][j]=max(l[i][j],l[i-1][j]);
r[i][j]=min(r[i][j],r[i-1][j]);
height[i][j]=height[i-1][j]+1;
}
int x=r[i][j]-l[i][j]+1;
int y=min(x,height[i][j]);//最大的合法正方形一定在合法的矩形内
max1=max(max1,yy);
max2=max(max2,xheight[i][j]);
}
}
cout<<max1<<endl<<max2;
return 0;
}