最大子矩阵扫描法和悬线法
Posted 行码棋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最大子矩阵扫描法和悬线法相关的知识,希望对你有一定的参考价值。
- 博客主页: https://blog.csdn.net/qq_50285142
- 欢迎点赞👍收藏✨关注❤留言 📝 如有错误,敬请指正
- 🎈虽然生活很难,但我们也要一直走下去🎈
最大子矩阵问题
在一个给定的 n ∗ m n*m n∗m矩形网格中有 n n n个障碍点,要找出网格内部不包含任何障碍点,且边界与坐标轴平行的最大子矩形。
扫描法
题意:
n ∗ m n*m n∗m的牧场里有一些产奶点,先要建造一个矩形的浴场,里面不能包含产奶点,产奶点可以在浴场边缘,求建造的最大面积
性质:
1.极大有效子矩形的四条边无法向四边拓展, 也就是说四条边都存在障碍点或与大矩形边界重合
2.存在一个障碍点的矩形中最大有效子矩形一定是极大有效子矩形
算法复杂度: O ( n 2 ) O(n^2) O(n2)
我们先考虑从上到下扫描点:
我们要维护一个左右边界l
,r
,保证左右边界之间没有障碍点存在
如果访问到的点j
的纵坐标大于当前点i
的纵坐标,更新右边界,取最小值
如果访问到的点j
的纵坐标小于当前点i
的纵坐标,更新左边界,取最大值
更新结果:
r
e
s
=
m
a
x
(
r
e
s
,
(
r
−
l
)
∗
(
p
[
j
]
.
f
i
−
p
[
i
]
.
f
i
)
)
res = max(res,(r-l)*(p[j].fi-p[i].fi))
res=max(res,(r−l)∗(p[j].fi−p[i].fi))为左右边界之间的差乘上访问点j
和当前点i
的x
坐标之差,即为更新了 以访问点j
和当前点i
为上下的边界,以维护的左右l
,r
为左右边界所组成矩形的面积
然后从左到右扫描所有的点和上面时类似的道理
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef pair<int,int> pii;
const int N = 5005;
pii p[N];
int res;
int n,m,k;
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=k;i++)
cin>>p[i].fi>>p[i].se;
//加入矩形的四个点
p[++k] = {0,0};
p[++k] = {0,m};
p[++k] = {n,0};
p[++k] = {n,m};
sort(p+1,p+1+k,[](pii a,pii b){
return a.fi<b.fi;
});//按x坐标排序
int l,r,v;//左右边缘
for(int i=1;i<=k;i++)
{
l = 0,r = m,v = n-p[i].fi;
for(int j=i+1;j<=k;j++)
{
if(v * (r-l) < res) break;//剪枝
res = max(res,(r-l)*(p[j].fi-p[i].fi));
if(p[j].se >= p[i].se) r = min(r,p[j].se);
else l = max(l,p[j].se);
}
}
sort(p+1,p+1+k,[](pii a,pii b){
return a.se < b.se;
});//按y坐标排序
int u,d;//上下边缘
for(int i=1;i<=k;i++)
{
u = 0,d = n,v = m-p[i].se;
for(int j=i+1;j<=k;j++)
{
if(v * (d-u) < res) break;
res = max(res,(d-u)*(p[j].se-p[i].se));
if(p[j].fi >= p[i].fi) d = min(d,p[j].fi);
else u = max(u,p[j].fi);
}
}
cout<<res<<'\\n';
return 0;
}
悬线法
悬线:该点到上端的最长连续0长度
设 H [ i , j ] H[i,j] H[i,j]为点 ( i , j ) (i,j) (i,j)对应的悬线的长度。
L [ i , j ] L[i,j] L[i,j]为点 ( i , j ) (i,j) (i,j)对应的悬线向左最多能够移动到的位置。
R
[
i
,
j
]
R[i,j]
R[i,j]为点
(
i
,
j
)
(i,j)
(i,j)对应的悬线向右最多能够移动到的位置。
我们要维护每一格对应向上的最大连续0的长度,就是所谓的悬线,还要维护该条悬线向左向右扩展的最远位置的下标,即为上面标明的三个值。
结果计算就是每一格的对应面积 ( r [ i ] [ j ] − l [ i ] [ j ] + 1 ) ∗ h [ i ] [ j ] (r[i][j]-l[i][j]+1)*h[i][j] (r[i][j]−l[i][j]+1)∗h[i][j],所有取一个最大值。
1.维护 h [ i ] [ j ] h[i][j] h[i][j]:
- 当前位为0时: h [ i ] [ j ] = h [ i − 1 ] [ j ] + 1 h[i][j] = h[i-1][j]+1 h[i][j]=h[i−1][j]+1,上面的一位的长度加一
- 当前位为1时: h [ i ] [ j ] = 0 h[i][j]=0 h[i][j]=0,因为初始化就为0,就没有操作
2.维护 l [ i ] [ j ] l[i][j] l[i][j]:
需要额外声明一个 l l ll ll变量表示该行左侧 0最远到达的下标 l l ll ll,因为访问该行时,遇见1然后 l l ll ll就会更新为1的右面 j + 1 j+1 j+1的位置,碰见0就和上一行悬线左侧最远到达的位置进行最大更新
- 当前位为0时: l [ i ] [ j ] = m a x ( l [ i − 1 ] [ j ] , l l ) l[i][j] = max(l[i-1][j],ll) l[i][j]=max(l[i−1][j],ll),使悬线位置最远,即x坐标最大
- 当前位为1时: l [ i ] [ j ] = 1 l[i][j]=1 l[i][j]=1,更新 l l ll ll为 j + 1 j+1 j+1
3.维护 r [ i ] [ j ] r[i][j] r[i][j]
需要从右边维护,道理和上面的相似
- 当前位为0时: r [ i ] [ j ] = m i n ( r [ i − 1 ] [ j ] , r r ) r[i][j] = min(r[i-1][j],rr) r[i][j]=min(r[i−1][j],rr)
- 当前位为1时:
r
[
i
]
[
j
]
=
n
r[i][j]=n
r[i][j]=n。n为初始值,保证递推的时候在最右端,如果访问到下面为0的位置的话,
r
[
i
−
1
]
[
j
]
r[i-1][j]
r[i−1][j]代表的就是为1的位,保证
r
[
i
]
[
j
]
=
m
i
n
(
r
[
i
−
1
]
[
j
]
,
r
r
)
r[i][j] = min(r[i-1][j],rr)
r[i][j]=min(r[i−1][j],rr)中的
r
[
i
−
1
]
[
j
]
r[i-1][j]
r[i−1][j]总是最大的,然后就可以取
rr
这个值(本行的到达最右端的位置)
维护完就可以计算结果了
另外本题需要使用滚动数组才能过掉,先理解二维,然后压缩成一维就可以了
MLE代码:(重在理解递推)
#include<bits/stdc++.h>
using namespace std;
const int N = 2005;
int a[N][N];
int res,n;
int h[N][N],l[N][N],r[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
{
int ll=1,rr=n;
for(int j=1;j<=n;j++)
{
if(a[i][j]) ll = j+1,l[i][j] = 1;
else h[i][j] = h[i-1][j]+1,l[i][j]=max(l[i-1][j],ll);
}
for(int j=n;j>=1;j--)
{
if(a[i][j]) rr = j-1,r[i][j] = n;
else r[i][j] = min(rr,r[i-1][j]);
res = max(res,(r[i][j]-l[i][j]+1)*h[i][j]);
}
}
cout<<res<<'\\n';
return 0;
}
AC代码(上述代码优化为滚动数组)
#include<bits/stdc++.h>
using namespace std;
const int N = 2005;
int a[N][N];
int res,n;
int h[N],l[N],r[N];
动态规划之悬线法模板(最大子矩阵问题)