悬线法最早由王知昆dalao在IOI2003年国家集训队论文-《浅谈用极大化思想解决最大子矩形问题》中最早提出,用于解决最大(最优)子矩阵及相关变形问题。

定义

  1. 有效子矩阵为内部不包含任何障碍点,且边界与坐标轴平行的子矩阵;
  2. 极大有效子矩阵:如果一个有效子矩阵如果不包含它且比它大的有效子矩阵,则为极大有效子矩阵;
  3. 最大有效子矩阵:所有有效子矩阵中最大面积的子矩阵

极大化思想

定理一

在一个有障碍点的矩形中的最大子矩阵一定是一个极大子矩阵

定理二

一个极大子矩阵的四边一定不能向外扩展,即要么每条边碰触到了障碍点或者矩形边界。

算法一

  1. 枚举极大子矩阵的左边界,从左到右依次扫描每一个障碍点
  2. 不断修改可行的上下边界,从而得出所有以这个定点为左边界的极大子矩阵
  3. 将点按纵坐标排序,从上到下再做同样的扫描

时间复杂度为$O(S^2)$; 当障碍点多的时候,效率比较慢。

定义2

  1. 有效竖线:除了俩端点外,不碰触任何障碍点的竖直线段;
  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 } 
View Code

相关文章: