时间限制: 1.0 秒
空间限制: 512 MiB
题目背景
顿顿在学习了数字图像处理后,想要对手上的一副灰度图像进行降噪处理。不过该图像仅在较暗区域有很多噪点,如果贸然对全图进行降噪,会在抹去噪点的同时也模糊了原有图像。因此顿顿打算先使用邻域均值来判断一个像素是否处于较暗区域,然后仅对处于较暗区域的像素进行降噪处理。
题目描述
待处理的灰度图像长宽皆为 n 个像素,可以表示为一个 n×n 大小的矩阵 A,其中每个元素是一个 [0,L) 范围内的整数,表示对应位置像素的灰度值。 对于矩阵中任意一个元素 Aij(0≤i,j<n),其邻域定义为附近若干元素的集和:
Neighbor(i,j,r)={Axy|0≤x,y<n and |x−i|≤r and |y−j|≤r}
这里使用了一个额外的参数 r 来指明 Aij 附近元素的具体范围。根据定义,易知 Neighbor(i,j,r) 最多有 (2r+1)2 个元素。
如果元素 Aij 邻域中所有元素的平均值小于或等于一个给定的阈值 t,我们就认为该元素对应位置的像素处于较暗区域。 下图给出了两个例子,左侧图像的较暗区域在右侧图像中展示为黑色,其余区域展示为白色。
现给定邻域参数 r 和阈值 t,试统计输入灰度图像中有多少像素处于较暗区域。
输入格式
从标准输入读入数据。
输入共 n+1 行。
输入的第一行包含四个用空格分隔的正整数 n、L、r 和 t,含义如前文所述。
第二到第 n+1 行输入矩阵 A。 第 i+2(0≤i<n)行包含用空格分隔的 n 个整数,依次为 Ai0,Ai1,⋯,Ai(n−1)。
输出格式
输出到标准输出。
输出一个整数,表示输入灰度图像中处于较暗区域的像素总数。
样例1输入
4 16 1 6
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
样例1输出
7
样例2输入
11 8 2 2
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 7 0 0 0 7 0 0 7 7 0
7 0 7 0 7 0 7 0 7 0 7
7 0 0 0 7 0 0 0 7 0 7
7 0 0 0 0 7 0 0 7 7 0
7 0 0 0 0 0 7 0 7 0 0
7 0 7 0 7 0 7 0 7 0 0
0 7 0 0 0 7 0 0 7 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
样例2输出
83
子任务
70 的测试数据满足 n≤100、r≤10。
全部的测试数据满足 0<n≤600、0<r≤100 且 2≤t<L≤256。
思路
一开始我想着这个题是第二题,应该可以暴力求解,于是直接二重循环,但是提交后发现只通过了7个测试用例,后3个用例超时了(该思路的代码见代码1)。
于是我仔细读了一下题目,发现子任务那里写着:70 的测试数据满足 n≤100、r≤10;而全部的测试数据满足 0<n≤600、0<r≤100 且 2≤t<L≤256。看到这里我就知道了,后三个测试用例是卡时间的,暴力并不能拿到满分,必须进行优化。
思考这个过程,我们会发现,其实对于某个元素的邻域来说,它可以由前一个元素的邻域和一些新的元素构成,因此大量重复的加和运算可被避免。
举个例子:
当L = 50,n = 7,r = 2,t任意时,给出矩阵A:
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 32 33 34 35
36 37 38 39 40 41 42
43 44 45 46 47 48 49
我们分别考察24和25(因为这两个元素是相邻的)的邻域,应该分别是
1 2 3 4 5 6 7
— — — — — — —
| 8 9 10 11 12 | 13 14
| 15 16 17 18 19 | 20 21
| 22 23 24 25 26 | 27 28
| 29 30 31 32 33 | 34 35
| 36 37 38 39 40 | 41 42
— — — — — — —
43 44 45 46 47 48 49
24的邻域
1 2 3 4 5 6 7
— — — — — — —
8 | 9 10 11 12 13 | 14
15 | 16 17 18 19 20 | 21
22 | 23 24 25 26 27 | 28
29 | 30 31 32 33 34 | 35
36 | 37 38 39 40 41 | 42
— — — — — — —
43 44 45 46 47 48 49
25的邻域
可以看到,这两个元素的邻域之间的重合度非常高,因此可以通过利用前一个元素的邻域来避免大量重复计算。具体思路是:每一行的第一个元素执行一次完整的邻域计算,对于之后的n - 1个元素,利用前一个元素的邻域进行计算。如此,执行时间大大降低,成功通过所有测试用例。
代码1(暴力遍历)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n, L, r, t;
vector<vector<int>> matrix;
int temp;
int res = 0;
cin >> n >> L >> r >> t;
//input
for(int i = 0; i < n; i++)
{
vector<int> a;
for(int j = 0; j < n; j++)
{
cin >> temp;
a.push_back(temp);
}
matrix.push_back(a);
}
//data process
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
{
int bound_x = i + r > n - 1? n - 1 : i + r;
int bound_y = j + r > n - 1? n - 1 : j + r;
int sum = 0, num = 0;
for(int k = i - r > 0? i - r : 0; k <= bound_x; k++)
for(int m = j - r > 0 ? j - r : 0; m <= bound_y; m++)
{
num++;
sum += matrix[k][m];
}
double avg = (double)sum / num;
if(avg <= t)
res++;
}
cout << res;
return 0;
}
代码2(优化后的代码)
#include <iostream>
using namespace std;
int main()
{
int n, L, r, t;
int matrix[600][600];
int res = 0;
int temp;
cin >> n >> L >> r >> t;
//input
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
cin >> temp;
matrix[i][j] = temp;
}
}
//data process
int sum = 0, num = 0, lastsum = 0, lastnum = 0;
for(int i = 0; i < n; i++)
{
sum = num = 0;
int bound_x = i + r > n - 1? n - 1 : i + r;
int bound_y = 0 + r > n - 1? n - 1 : 0 + r;
int start_x = i - r > 0 ? i - r : 0;
for(int k = start_x; k <= bound_x; k++)
for(int m = 0; m <= bound_y; m++)
{
num++;
sum += matrix[k][m];
}
double avg = (double)sum / num;
lastnum = num; lastsum = sum;
if(avg <= t)
res++;
for(int j = 1; j < n; j++)
{
sum = lastsum;
if(j - r <= 0)
{
num = lastnum + bound_x - start_x + 1;
for(int m = start_x; m <= bound_x; m++)
sum += matrix[m][j+r];
avg = (double)sum / num;
}
else if(j + r > n - 1)
{
num = lastnum - (bound_x - start_x + 1);
for(int m = start_x; m <= bound_x; m++)
sum -= matrix[m][j - r - 1];
avg = (double)sum / num;
}
else
{
num = lastnum;
for(int m = start_x; m <= bound_x; m++)
sum += matrix[m][j+r] - matrix[m][j - r - 1];
avg = (double)sum / num;
}
if(avg <= t)
res++;
lastnum = num; lastsum = sum;
}
}
cout << res;
return 0;
}