zoukankan      html  css  js  c++  java
  • 悬线法-专题训练

    悬线法简介

    悬线法最早由王知昆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

    解法二:dp

    动规转移方程:

    $if (a[i][j]==1) f[i][j]=min(min(f[i][j-1],f[i-1][j]),f[i-1][j-1])+1$

     

    如果不懂,请看图,一个正方形的形成,最小是1,如果要扩大,则必须两边都满足自己是子正方形。

    定义

    1. 最小的正方形:当当前格为1时,可形成最小的正方形,边长为1,面积为1;
    2. 子正方形:当一个正方形包含(小于或等于其边长)的正方形时,成为其含有子正方形;

    定理一

    每个正方形都包含子正方形,包括自己。

    定理二

    每个正方形的最小子正方形为最小的正方形,且每个正方形都由子正方形扩展而来

    算法

     如果要获得最大正方形,则由定理二可知必须由子正方形扩展而来,且必须由最小正方形扩展而来。从左上到右下,最小的正方形显而易见是单独的1,扩展的充分必要条件是在边长-1的子正方形基础上,两侧的边长都有1,即可以被扩展。

     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,ans=0,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         {
    20             cin>>arr[i][j];
    21         }
    22     FOR(i,1,n+1)
    23         FOR(j,1,m+1)
    24         {
    25             if(arr[i][j]==1)//表示可以作为正方形的右下角 
    26             dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
    27             ans=max(ans,dp[i][j]);
    28         }
    29     cout<<ans<<endl;
    30     return 0;
    31 }
    View Code

    解法三:悬线法

    关于初始化$l,r,up$数组

     1 for(int i=1;i<=n;i++) 
     2     for(int j=1;j<=m;j++)
     3         读入,right[i][j]=left[i][j]=j,up[i][j]=1;
     4 for(int i=1;i<=n;i++)
     5     for(int j=2;j<=m;j++)
     6         if(满足条件)
     7             right[i][j]=right[i][j-1];
     8 for(int i=1;i<=n;i++)
     9     for(int j=m-1;j>=1;j--)
    10         if(满足条件)
    11             left[i][j]=left[i][j+1]; 

    关于计算最优值

    1 if(满足条件){
    2     right[i][j]=min(right[i][j],right[i-1][j]);
    3     left[i][j]=max(left[i][j],left[i-1][j]);
    4     up[i][j]=up[i-1][j]+1;
    5 }

    不多说,又快又准确,具体看注释

     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[MAXN][MAXN];
    14 int l[MAXN][MAXN],r[MAXN][MAXN],up[MAXN][MAXN],ans1,ans2;
    15 int main()
    16 {
    17     cin>>n>>m;
    18     FOR2(i,1,n)
    19         FOR2(j,1,m)//初始化 
    20             cin>>arr[i][j],l[i][j]=r[i][j]=j,up[i][j]=1;
    21     FOR2(i,1,n)
    22         FOR2(j,2,m)//获得左边界 
    23             if(arr[i][j]&&arr[i][j]==arr[i][j-1]) l[i][j]=l[i][j-1];
    24     FOR2(i,1,n)
    25         for(int j=m-1;j>=1;j--)//获得右边界 
    26             if(arr[i][j]&&arr[i][j]==arr[i][j+1])r[i][j]=r[i][j+1];
    27     FOR2(i,1,n)
    28         FOR2(j,1,m)
    29         {
    30 //            if(i>1)
    31             {
    32                 if(arr[i][j]==arr[i-1][j]&&arr[i][j]==1)
    33                 {//向下扩展悬线 
    34                     l[i][j]=max(l[i][j],l[i-1][j]);//取最右边的左边界 
    35                     r[i][j]=min(r[i][j],r[i-1][j]);//取最左边的右边界 
    36                     up[i][j]=up[i-1][j]+1;//高度+1 
    37                 }
    38                 int a=r[i][j]-l[i][j]+1;//求宽 
    39                 int b=min(a,up[i][j]);//求高和宽的最小值,可以构成正方形 
    40                 ans1=max(b,ans1);//更新答案 
    41             }
    42         }
    43     cout<<ans1<<endl;
    44     return 0;
    45 }
    View Code

    洛谷P4147玉蟾宫

    此题也可以用单调栈解决,详见我的另外一篇介绍单调栈的博文

     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[MAXN][MAXN];
    14 int l[MAXN][MAXN],r[MAXN][MAXN],up[MAXN][MAXN],ans=0;
    15 int main()
    16 {//100ms
    17     cin>>n>>m;
    18     FOR2(i,1,n)
    19         FOR2(j,1,m)
    20             {
    21                 char ch;cin>>ch;while(ch==' '||ch=='
    ')cin>>ch;
    22                 if(ch=='R')arr[i][j]=0;
    23                 else arr[i][j]=1;
    24                 l[i][j]=r[i][j]=j,up[i][j]=1;//读入 
    25             }
    26     FOR2(i,1,n)
    27         FOR2(j,2,m)
    28         {
    29             if(arr[i][j]&&arr[i][j]==arr[i][j-1])    l[i][j]=l[i][j-1];
    30         }    
    31     FOR2(i,1,n)
    32         for(int j=m-1;j>=1;j--)
    33         {
    34             if(arr[i][j]&&arr[i][j]==arr[i][j+1])    r[i][j]=r[i][j+1];
    35         }
    36     FOR2(i,1,n)
    37         FOR2(j,1,m)
    38         {
    39             if(arr[i][j]&&arr[i][j]==arr[i-1][j])
    40             {
    41                 up[i][j]=up[i-1][j]+1;
    42                 l[i][j]=max(l[i][j],l[i-1][j]);
    43                 r[i][j]=min(r[i][j],r[i-1][j]);
    44             }
    45 //            cout<<i<<" "<<j<<" "<<r[i][j]<<" "<<l[i][j]<<" "<<up[i][j]<<endl;
    46             int size=(r[i][j]-l[i][j]+1)*up[i][j];
    47             ans=max(ans,size);
    48         }
    49     cout<<ans*3<<endl;
    50     return 0;
    51 } 
    View Code

    洛谷P1169棋盘制作

    注意此题的条件是前后相异,需要更改满足条件

     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 2020
    11 #define MOD 10007
    12 using namespace std;
    13 int n,m,arr[MAXN][MAXN];
    14 int l[MAXN][MAXN],r[MAXN][MAXN],up[MAXN][MAXN],ans1=0,ans2=0;
    15 int main()
    16 {
    17     cin>>n>>m;
    18     FOR2(i,1,n)
    19         FOR2(j,1,m)
    20         {
    21             cin>>arr[i][j];
    22             l[i][j]=r[i][j]=j,up[i][j]=1;
    23         }
    24     FOR2(i,1,n)
    25         FOR2(j,2,m)
    26         {
    27             if(arr[i][j]^arr[i][j-1])l[i][j]=l[i][j-1];
    28         }
    29     FOR2(i,1,n)
    30         for(int j=m-1;j>=1;j--)
    31         {
    32             if(arr[i][j]^arr[i][j+1])r[i][j]=r[i][j+1];
    33         }
    34     FOR2(i,1,n)
    35         FOR2(j,1,m)
    36         {
    37             if(arr[i][j]^arr[i-1][j])
    38             {
    39                 up[i][j]=up[i-1][j]+1;
    40                 l[i][j]=max(l[i][j],l[i-1][j]);
    41                 r[i][j]=min(r[i][j],r[i-1][j]);
    42             }
    43 
    44             int size1=(r[i][j]-l[i][j]+1);
    45 //            cout<<size1<<" "<<up[i][j]<<endl;
    46             ans1=max(ans1,min(size1,up[i][j]));
    47             int size2=size1*up[i][j];
    48             ans2=max(ans2,size2);
    49         }
    50         cout<<ans1*ans1<<endl<<ans2<<endl;
    51     return 0;
    52 }
    View Code

    洛谷P2701巨大的牛棚

     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 2020
    11 #define MOD 10007
    12 using namespace std;
    13 int arr[MAXN][MAXN],n,t,a,b,ans=0;
    14 int l[MAXN][MAXN],r[MAXN][MAXN],up[MAXN][MAXN];
    15 int main()
    16 {
    17     cin>>n>>t;
    18     FOR2(i,1,n)
    19         FOR2(j,1,n)
    20         {
    21             arr[i][j]=1;l[i][j]=r[i][j]=j;up[i][j]=1;    
    22         }    
    23     FOR2(i,1,t)
    24     {//读入障碍 
    25         cin>>a>>b;
    26         arr[a][b]=0;
    27     }        
    28     FOR2(i,1,n)
    29         FOR2(j,2,n)
    30             if(arr[i][j]&&arr[i][j]==arr[i][j-1])
    31                 l[i][j]=l[i][j-1];
    32     FOR2(i,1,n)
    33         for(int j=n-1;j>=1;j--)
    34             if(arr[i][j]&&arr[i][j+1]==arr[i][j])
    35                 r[i][j]=r[i][j+1];
    36     FOR2(i,1,n)
    37     {
    38         FOR2(j,1,n)
    39         {
    40             if(arr[i][j]&&arr[i][j]==arr[i-1][j])
    41             {
    42                 up[i][j]=up[i-1][j]+1;
    43                 l[i][j]=max(l[i][j],l[i-1][j]);
    44                 r[i][j]=min(r[i][j],r[i-1][j]);
    45             }
    46             int size=r[i][j]-l[i][j]+1;
    47 //            cout<<size<<" "<<up[i][j]<<endl;
    48             ans=max(ans,min(size,up[i][j]));
    49         }
    50 //        cout<<endl;
    51     }
    52         
    53     cout<<ans<<endl;    
    54     return 0;
    55 }
    View Code

    洛谷P1578奶牛浴场

    此题不能使用悬线法的解法,因为$(3*10^4)^2$的数组开不下,加上障碍点远小于$n*m$,使用极大化思想的算法一可以解决

  • 相关阅读:
    第12课 计算器核心解析算法(上)
    第11课 Qt中的字符串类
    第10课 初探 Qt 中的消息处理
    第9课 计算器界面代码重构
    第8课 启航!第一个应用实例
    第7课 Qt中的坐标系统
    第6课 窗口部件及窗口类型
    第5课 Qt Creator工程介绍
    第4课 Hello QT
    给Linux内核增加一个系统调用的方法(转)
  • 原文地址:https://www.cnblogs.com/tldr/p/11315884.html
Copyright © 2011-2022 走看看