讲解,讲的很好,不赘述了,补充点东西,放一下例题代码。
悬线法求的主要是个求最大矩形的问题,属于优化动态规划的一种思想。
这里变量名和状态转移方程均沿用上面的讲解博客的。 l [ i ] [ j ] l[i][j] l[i][j] 数组表示在位置 ( i , j ) (i,j) (i,j) 开始直接向左走最远能走到哪里, L [ i ] [ j ] L[i][j] L[i][j] 数组表示在位置 ( i , j ) (i,j) (i,j) 的悬线向左最远可以移动到哪里。这里的递推式子可以这样理解:位置 ( i , j ) (i,j) (i,j) 的悬线和位置 ( i − 1 , j ) (i-1,j) (i−1,j) 的悬线的上端点是一样的,因此位置 ( i , j ) (i,j) (i,j) 的悬线要么在第 i i i 行更早碰到障碍,要么在第 i − 1 i-1 i−1 行到悬线上端点这一块碰到障碍,因此递推方程就是 L [ i ] [ j ] = m i n ( l [ i ] [ j ] , L [ i − 1 ] [ j ] ) L[i][j]=min(l[i][j],L[i-1][j]) L[i][j]=min(l[i][j],L[i−1][j])。同理右端点。
这个东西还可以用单调栈来维护,可以优化掉 l , r , L , R l,r,L,R l,r,L,R 数组, u u u 数组可以在读入数据的时候直接顺便处理好,这样就可以少写两个双重for循环。
考虑大概是个什么情况下会是一个极大矩形,假设我们已经处理好了悬线数组 u u u,现在枚举到了第 i i i 行,正在从左到右枚举列 j j j。 u [ i ] [ j ] u[i][j] u[i][j] 表示的是 矩形的宽 是这个悬线的 极大矩形。既然这个矩形极大,宽也确定(就是悬线长),那么肯定左右两边的悬线长度都比它小。
那么如果我们单调栈维护的悬线长度单调递增,那么我们在加入上图中的绿色悬线的时候就会把红色悬线从单调栈中踢出去,这时候就形成了上面的这种情形。要加入的悬线就是右边的绿色悬线,要出栈的悬线就是红色悬线,栈顶下一个就是蓝色悬线。
所以对每一行,从左到右跑一遍单调栈,如果要取出栈顶元素,就相当于产生了一个极大矩形,记录一下答案即可。需要注意的是,有时在出栈的时候栈中就只有一个元素,没有下一个元素了,这表示左边没有悬线更小的情形,把左边界当作悬线长度为0的悬线即可。在跑完一行的单调栈后,栈中会留下一些元素,这表示右边没有悬线更小的情形,把右边界第 m + 1 m+1 m+1 列看作悬线长度为0的元素加入维护单调栈即可,代码片段如下:
ans=0;
for(int i=1;i<=n;i++){
top=0;
u[i][m+1]=0;
for(int j=1;j<=m+1;j++){
while(top && u[i][sta[top]]>u[i][j]){
ans=max(ans,(j-sta[top-1]-1)*(u[i][sta[top]]+1));
top--;
}
sta[++top]=j;
}
}
例题代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=105;
int n,m,a[maxn][maxn];
int l[maxn][maxn],r[maxn][maxn],u[maxn][maxn];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j]){
u[i][j]=(i>1 && a[i-1][j])?u[i-1][j]+1:1;
l[i][j]=(j>1 && a[i][j-1])?l[i][j-1]+1:1;
}
for(int i=1;i<=n;i++)
for(int j=m-1;j>=1;j--)
if(a[i][j])
r[i][j]=(a[i][j+1])?r[i][j+1]+1:1;
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(i>1 && a[i-1][j]){
l[i][j]=min(l[i][j],l[i-1][j]);
r[i][j]=min(r[i][j],r[i-1][j]);
}
ans=max(ans,min(u[i][j],r[i][j]+l[i][j]-1));
}
cout<<ans;
return 0;
}
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2005;
int n,m;
int a[maxn][maxn];
int u[maxn][maxn],l[maxn][maxn],r[maxn][maxn];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
u[i][j]=(i>1 && (a[i-1][j]^a[i][j]))?u[i-1][j]+1:1;
l[i][j]=(j>1 && (a[i][j-1]^a[i][j]))?l[i][j-1]+1:1;
}
for(int i=1;i<=n;i++)
for(int j=m-1;j>=1;j--)
r[i][j]=(a[i][j+1]^a[i][j])?r[i][j+1]+1:1;
int ans1=0,ans2=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(i>1 && (a[i-1][j]^a[i][j])){
l[i][j]=min(l[i][j],l[i-1][j]);
r[i][j]=min(r[i][j],r[i-1][j]);
}
int t=min(u[i][j],l[i][j]+r[i][j]-1);
ans1=max(ans1,t*t);
ans2=max(ans2,u[i][j]*(l[i][j]+r[i][j]-1));
}
cout<<ans1<<endl<<ans2<<endl;
return 0;
}
这个时限只有0.4s,还要跑 O ( n 2 ) O(n^2) O(n2),我是找不到什么更好的优化方式了,一直TLE,网上翻到的代码也全军覆没,不能保证代码正确性。
上面的博客是在行之间插入0或1。实际上不需要插入。把行之间的0/1单独取出来,这是个 ( n − 1 ) ∗ m (n-1)*m (n−1)∗m 的数组,在这个数组上跑最大的 1 1 1 矩形即可。
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2005;
int T,n,m,ans;
int a[maxn][maxn],u[maxn][maxn];
int sta[maxn],top;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
if(a[i][j]>=a[i-1][j])u[i][j]=u[i-1][j]+1;
else u[i-1][j]=0;
}
n--;
ans=0;
for(int i=1;i<=n;i++){
top=0;
u[i][m+1]=0;
for(int j=1;j<=m+1;j++){
while(top && u[i][sta[top]]>u[i][j]){
ans=max(ans,(j-sta[top-1]-1)*(u[i][sta[top]]+1));
top--;
}
sta[++top]=j;
}
}
printf("%d\n",ans);
}
return 0;
}