P7410 题解

题解 · 2023-08-26

备注:此做法来自nzq巨佬

题意:

FJ 有一个边长为 $n (n \leq 500)$, 共有 $n^2$ 个格子的正方形草地,每一个格子有一个 $1...200$ 的绿度值。对于 FJ 的这个草地,有很多个子矩阵。现在需要你求出子矩阵中所有格子的最小值是 $100$ 的子矩阵的个数。

样例:

输入

3
57 120 87
200 100 150
2 141 135

输出

8

_

思路

我们可以发现答案显然等于最小值小于 $100$ 的矩阵的个数减去最小值小于等于 $100$ 的个数。

如何计算这两个值呢?

我们设函数 query(m) 为最小值小于 $m$ 的子矩阵个数,那么答案就等于 query(100)-query(101)

这个函数怎么写呢,我们将所有小于 $m$ 的格子设为 $1$, 将所有大于等于 $m$ 的格子设为 $0$, 我们对于每一行记一个 $sum_{i,j}$。

代码如下:

for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
        if(a[i][j]<maxx)b[i][j]=1,sum[i][j]=0;
        else sum[i][j]=sum[i][j-1]+1,b[i][j]=0;
    }
}

其中 $a$ 表示该格子的绿度值, $b_{i,j}=1$ 时表示该格子的绿度值小于 $m$ 等于 $1$ 则表示该格子大于 $m$。

显然,如果 $b_{i,k \ldots j}$ 都为 $1$ 时, $sum_{i,j}=j-k+1$, 如果 $b_{i,j}=0$ 则 $sum_{i,j}=0$。

记录完了 $sum$ 和 $b$ 后,我们枚举每一个格子,我们把这个格子作为一个子矩阵的右下角,为了方便,用 $x$ 表示。那么能与 $x$ 符合的左端点有几个呢?我们发现符合条件的子矩阵需要满足它每一个格子都要大于等于 $m$, 这时候我们统计的 $sum$ 就有用了,我们枚举所有与 $x$ 在同一列且在 $x$ 上方的格子(即行数小于等于 $x$ 的格子),我们看这些格子在 $sum$ 数组中的最小值。将每个最小值累加起来就是 query(m) 的答案了。

AC代码:

#include<bits/stdc++.h>
#pragma G++ optimize(3)
using namespace std;
#define inf 1000000010
const int maxn=510;
int n,a[maxn][maxn],b[maxn][maxn],sum[maxn][maxn];
long long query(int maxx){
    long long ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(a[i][j]<maxx)b[i][j]=1,sum[i][j]=0;
            else sum[i][j]=sum[i][j-1]+1,b[i][j]=0;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(b[i][j])continue;
            long long Min=inf;
            for(int k=i;k>=1;k--){
                Min=min(Min,(long long)sum[k][j]);
                ans+=Min;
            }
        }
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&a[i][j]);
        }
    }
    printf("%lld",query(100)-query(101));
    return 0;
}
Theme Jasmine by Kent Liao