首先,说明一下模型:
有一个(n*m)的矩阵,矩阵中的有些点有权值(大于0),还有一些点是障碍物(权值为0).
而我们要选出一个不含障碍物的子矩阵,使这个子矩阵的权值和最大.
那么,我们直接进入正题:
悬线法
首先,让我们了解一下悬线:
我们让一个点向上拓展,直到遇到障碍物位置,
那么拓展的长度就是它的悬线长度.
而悬线长度也很好求,用递推即可.
设(h[i][j])表示点((i,j))的悬线长度,
若((i,j))不为障碍物,则(h[i][j])=(h[i)-(1)(][j])+1.
否则,(h[i][j])=0.
这一部分的代码:
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) h[i][j]= a[i][j]? 0:h[i-1][j]+1;//递推计算悬线长度
那么,对于每个点,我们再考虑将它的悬线左右平移,直到遇到障碍物.
那么,最大子矩形一定就在每个点的悬线平移后的矩形的集合里面,
而怎么平移呢?
我们设(l[i][j])表示点((i,j))的悬线能向左展开到达的第一个障碍物,(r[i][j])则是向右(注意,有些算法是求展开的长度,但这里是求的点),
以求向左为例,
我们先枚举每一行(i),并设(ret)为第(i)行当前的最后一个障碍物,
那么(ret)的初始值就是0,
再枚举每一列(j),
如果(h[i][j])不为0,即(j)不是障碍物,那么(l[i][j])=(max)((l[i)-(1)(][j]),(ret))
即当前离它最近的障碍物和它上面一个点的悬线能到达的障碍物中更近的一个
(没图可能不太清楚因为本蒟蒻画图太丑,自己画下图就能理解了).
而向右也一样,只是要取(min),并将(r[0][i])((i)=(1)~(m))预处理成(m)+1(即最后一列的后面)
此部分代码:
for(int i=1;i<=n;i++){
int ret=0;
for(int j=1;j<=m;j++){
if(h[i][j]) l[i][j]=max(l[i-1][j],ret);//求向左展开到达的点
else ret=j,l[i][j]=0;
}
ret=m+1;
for(int j=m;j;j--){
if(h[i][j]) r[i][j]=min(r[i-1][j],ret);//求向右展开到达的点
else ret=j,r[i][j]=n+1;
}
}
最后,枚举每个点移成的矩形,
用前缀和比较大小就行了.
代码:
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(!h[i][j]) continue;
int x1=i-h[i][j]+1;
int y1=l[i][j]+1,y2=r[i][j]-1;
if(sum(x1,y1,i,y2)<m) continue;
print_t(x1,y1,i,y2);
}
}
上完整代码吧(虽然似乎并没有什么用了):
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int sum=0,f=1;char ch=getchar();
while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0' && ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return f*sum;
}
int n,m;
ll ans=0;
ll a[2001][2001];//权值
ll s[2001][2001];//二维前缀和
int h[2001][2001]/*悬线*/,l[2001][2001]/*向左展开的位置*/,r[2001][2001]/*向右展开的位置*/;
inline ll sum(int x1,int y1,int x2,int y2)/*矩阵和*/{
return s[x2][y2]+s[x1-1][y1-1]-s[x1-1][y2]-s[x2][y1-1];
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
a[i][j]=read();
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
for(int i=1;i<=m;i++) r[0][i]=m+1;//预处理一下(因为后面要比较)
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) h[i][j]= a[i][j]>2*m? 0:h[i-1][j]+1;//递推计算悬线长度
for(int i=1;i<=n;i++){
int ret=0;
for(int j=1;j<=m;j++){
if(h[i][j]) l[i][j]=max(l[i-1][j],ret);//求向左展开到达的点
else ret=j,l[i][j]=0;
}
ret=m+1;
for(int j=m;j;j--){
if(h[i][j]) r[i][j]=min(r[i-1][j],ret);//求向右展开到达的点
else ret=j,r[i][j]=m+1;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(!h[i][j]) continue;
int x1=i-h[i][j]+1;
int y1=l[i][j]+1,y2=r[i][j]-1;
ans=max(ans,sum(x1,y1,i,y2));
}
}
printf("%lld
",ans);
return 0;
}