悬线法

使用一维悬线法找极大子矩形,通过极大子矩形统计矩形数量。

我一般使用悬线法找位置 $(i,j)$ 可以向上延伸的最大高度 $h_{i,j}$,在矩形高度为 $h_{i,j}$ 的情况下从 $(i,j)$ 最长可以向左延伸到的列 $l_{i,j}$ 和最长可以向右延伸到的列 $r_{i,j}$。然而这样统计长方形数量会不仅会算重还会算漏,因为当 $h_{i,j} \leq h_{i,j-1}$ 时可以用 $l_{i,j-1}$ 更新 $l_{i,j}$,当 $h_{i,j} \leq h_{i,j+1}$ 时又可以用 $r_{i,j+1}$ 更新 $r_{i,j}$,如此一来两个位置对应的极大子矩形可能是一样的。

为了不重不漏地计算答案,强制一边不能在相等高度的情况下扩展。把用 $r_{i,j+1}$ 更新 $r_{i,j}$ 的条件更改为 $h_{i,j} < h_{i,j+1}$,这样做之后任意两个位置对应的极大子矩形都是不同的。

位置 $(i,j)$ 对答案的贡献为包含 $(i,j)$ 且在 $(i,j)$ 对应的极大子矩形中的的矩形数量,即 $h_{i,j} (j - l_{i,j} + 1) (r_{i,j} - j + 1)$,时间复杂度 $O(nm)$。

#include<bits/stdc++.h>
using namespace std;
int n,m,h[1'005][1'005],l[1'005][1'005],r[1'005][1'005];
char s[1'005][1'005];
long long ans;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
        while(s[i][j]!='.'&&s[i][j]!='*') s[i][j]=getchar();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++) if(s[i][j]=='.') h[i][j]=h[i-1][j]+1;
        for(int j=1;j<=m;j++) if(s[i][j]=='.') l[i][j]=j,r[i][j]=j;
        for(int j=1;j<=m;j++) if(s[i][j]=='.')
            while(l[i][j]!=1&&h[i][l[i][j]-1]>=h[i][j]) l[i][j]=l[i][l[i][j]-1];
        for(int j=m;j>=1;j--) if(s[i][j]=='.')
            while(r[i][j]!=m&&h[i][r[i][j]+1]>h[i][j]) r[i][j]=r[i][r[i][j]+1];
        for(int j=1;j<=m;j++) ans+=1ll*h[i][j]*(j-l[i][j]+1)*(r[i][j]-j+1);
    }
    printf("%lld\n",ans);
    return 0;
}
最后修改:2024 年 10 月 12 日
如果觉得我的文章对你有用,请随意赞赏