悬线法最早由王知昆dalao在IOI2003年国家集训队论文-《浅谈用极大化思想解决最大子矩形问题》中最早提出,用于解决最大(最优)子矩阵及相关变形问题。
定义
- 有效子矩阵为内部不包含任何障碍点,且边界与坐标轴平行的子矩阵;
- 极大有效子矩阵:如果一个有效子矩阵如果不包含它且比它大的有效子矩阵,则为极大有效子矩阵;
- 最大有效子矩阵:所有有效子矩阵中最大面积的子矩阵
极大化思想
定理一
在一个有障碍点的矩形中的最大子矩阵一定是一个极大子矩阵
定理二
一个极大子矩阵的四边一定不能向外扩展,即要么每条边碰触到了障碍点或者矩形边界。
算法一
- 枚举极大子矩阵的左边界,从左到右依次扫描每一个障碍点
- 不断修改可行的上下边界,从而得出所有以这个定点为左边界的极大子矩阵
- 将点按纵坐标排序,从上到下再做同样的扫描
时间复杂度为$O(S^2)$; 当障碍点多的时候,效率比较慢。
定义2
- 有效竖线:除了俩端点外,不碰触任何障碍点的竖直线段;
- 悬线:上端点碰触障碍点或者达到矩形上端的有效竖线。
由定义2得出如下定理
定理三
将所有悬线向左右俩方向尽可能移动所得到的有效子矩阵的集合中,一定包含所有极大子矩阵。
算法二
由定理三可知,枚举所有的悬线,可以枚举出所有的极大子矩阵,时间复杂度为$O(n*m)$。
接下来,我们需要了解三个变量:悬线的高度,左移最大距离,右移最大距离。
假设对于底部为$(i,j)$的悬线,高度为$height[i,j]$,左移最大距离 为$left[i,j]$,右移最大距离为$right[i,j]$,面积为$height[i,j]*(right[i,j]-left[i,j]+1)$。
高度递推
$(i-1,j)$为障碍点,则$height[i,j]=height[i-1,j]+1$
左右边界递推
对于$left[i,j]和left[i-1,j]$,左边界始终取靠右的边界即 $left[i,j]=max(left[i,j],left[i-1,j])$;
右边界同理可得 $right[i,j]=min(right[i,j],right[i-1,j])$。
洛谷P1387最大正方形
解法一:暴力
记录矩阵的前缀和,可以发现$\sum{(i,j)}=(i,j)+\sum{(i-1,j)}+\sum{(i,j-1)}-\sum{(i-1,j-1)}$。以上面的矩形为例,$(4,4)$矩阵的前缀和是$(4,3)$加上$(3,4)$减去重复的$(3,3)$加上$(4,4)$点的值。
因此枚举左上角的点$(i,j)$和边长$e$,一共是三重循环,判断前缀和的差$\sum{(i+e-1,j+e-1)}-\sum{(i,j)}$是否等于边长的平方$e^2$。
1 #include<bits/stdc++.h> 2 #include<iostream> 3 #include<cstdio> 4 #include<stack> 5 #include<algorithm> 6 #define FOR(i,a,b) for(int i=a;i<b;i++) 7 #define FOR2(i,a,b) for(int i=a;i<=b;i++) 8 #define ll long long 9 #define INF 0x7f7f7f7f; 10 #define MAXN 2000 11 #define MOD 10007 12 using namespace std; 13 int n,m ,arr[200][200],dp[200][200]; 14 int main() 15 { 16 cin>>n>>m; 17 FOR(i,1,n+1) 18 FOR(j,1,m+1) 19 cin>>arr[i][j]; 20 FOR(i,1,n+1) 21 FOR(j,1,m+1) 22 dp[i][j]=arr[i][j]+dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]; 23 FOR(i,1,n+1) 24 { 25 FOR(j,1,m+1) 26 cout<<dp[i][j]<<" "; 27 cout<<endl; 28 } 29 int ans=0; 30 for(int i=1;i<=n;i++) 31 { 32 for(int j=1;j<=m;j++) 33 {//枚举左上角 34 for(int e=0;e<=min(n-i,m-j);e++) 35 {//枚举边长 36 int rx=i+e-1;int ry=j+e-1; 37 if(rx>n||ry>m)break; 38 if(dp[rx][ry]-dp[i-1][ry]-dp[rx][j-1]+dp[i-1][j-1]==e*e) 39 { 40 ans=max(ans,dp[rx][ry]-dp[i-1][ry]-dp[rx][j-1]+dp[i-1][j-1]); 41 // cout<<ans<<" "<<i<<" "<<j<<" "<<e<<endl; 42 } 43 } 44 } 45 } 46 cout<<(ll)sqrt(ans)<<endl; 47 return 0; 48 }