动态规划(DP)方法:悬线法

在动态规划问题中,有一类问题要求大矩阵的最大或最小子矩阵,这类问题往往可以采用悬线法,下面由一道例题来引入此法。
洛谷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,j1)两个点互相合法(在此例中是不相等), 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][j1] ,同理从右向左枚举时,如果 ( i , j ) 和 ( i , j − 1 ) (i,j)和(i,j-1) (i,j)(i,j1)两个点互相合法,则有 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,x
height[i][j]);
}
}
cout<<max1<<endl<<max2;
return 0;
}

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值