邻域均值

用二维前缀和快速计算每个裁剪邻域的总和,再比较均值阈值。

OJ: shumeng

题目 ID: CSP202104B

难度:普及-

标签:前缀和二维前缀和模拟

日期: 2026-07-31 16:21

形式化题目

给出一幅 n×nn\times n 的灰度矩阵,值域为 [0,L)[0,L)。对每个像素,其邻域是满足 xir|x-i|\le ryjr|y-j|\le r 的像素集合(越界部分裁剪掉)。统计邻域平均值不超过阈值 tt 的像素个数。

思路

朴素做法是对每个像素遍历整个邻域求和,复杂度为 O(n2r2)O(n^2r^2)。用二维前缀和可以把每个邻域的求和降为 O(1)O(1)

二维前缀和

sum[i][j]sum[i][j] 表示以 (1,1)(1,1)(i,j)(i,j) 为对角矩形的像素总和,递推式为

sum[i][j]=sum[i1][j]+sum[i][j1]sum[i1][j1]+A[i][j] sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+A[i][j]。

判断较暗区域

  1. 对像素 (i,j)(i,j),把邻域裁剪成矩形 [top,bottom]×[left,right][top,bottom]\times[left,right]
  2. 用前缀和 O(1)O(1) 求矩形内的总和与格子个数;
  3. sumt×countsum\le t\times count 比较,避免浮点误差。

先看一个直接枚举邻域格子的朴素解:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
// brute.cpp:小数据暴力解,枚举每个像素邻域中的全部格子,直接求和再比较均值。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 605;

int n, levels, radius, threshold;
int image[MAXN][MAXN];   // 原始灰度矩阵

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> levels >> radius >> threshold;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) cin >> image[i][j];
    }

    int answer = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            // 对每个像素,枚举其邻域内的所有格子求和
            int total = 0, count = 0;
            for (int x = max(1, i - radius); x <= min(n, i + radius); x++) {
                for (int y = max(1, j - radius); y <= min(n, j + radius); y++) {
                    total += image[x][y];
                    count++;
                }
            }
            if (total <= threshold * count) answer++;
        }
    }
    cout << answer << '\n';
    return 0;
}

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 605;

int n, levels, radius, threshold;
int sum[MAXN][MAXN];   // sum[i][j] 为以 (1,1) 到 (i,j) 为对角矩形的像素总和

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> levels >> radius >> threshold;
    // 边读入边构造二维前缀和
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            int value;
            cin >> value;
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + value;
        }
    }

    int answer = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            // 邻域在图像边界处裁剪为矩形
            int top = max(1, i - radius), bottom = min(n, i + radius);
            int left = max(1, j - radius), right = min(n, j + radius);
            int total = sum[bottom][right] - sum[top - 1][right] - sum[bottom][left - 1] + sum[top - 1][left - 1];
            int count = (bottom - top + 1) * (right - left + 1);
            // 用乘法比较避免浮点误差:均值 <= t 等价于 总和 <= t * 个数
            if (total <= threshold * count) answer++;
        }
    }
    cout << answer << '\n';
    return 0;
}

复杂度

建表和枚举像素均为 O(n2)O(n^2),空间复杂度为 O(n2)O(n^2)

总结

二维前缀和把每个邻域求和从遍历正方形降为四次数组访问;边界只需裁剪矩形端点。