总结:
在传统二维前缀和求解的思路上对领域边界进行了要求。我的方法是根据r补零,确保区域求和公式不变,变领域数量,但边界问题难分析。更优的方法是补一圈0,根据领域数量变区域求和公式。
一、题目要求
问题描述
待处理的灰度图像长宽皆为 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)。
输出格式
输出一个整数,表示输入灰度图像中处于较暗区域的像素总数。
样例输入
4 16 1 6
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
Data
样例输出
7
二、我的解法
1、暴力求解(70)
#include<iostream>
using namespace std;
int dx[4]={0,0,-1,1};
int dy[4]={1,-1,0,0};
int a[601][601];
int main(){
int n,l,r,t;
cin>>n>>l>>r>>t;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
cin>>a[i][j];
}
int res=0;
for(int x=0;x<n;x++)
for(int y=0;y<n;y++){
int sum=0,num=0;
for(int i=x-r;i<=x+r;i++){
for(int j=y-r;j<=y+r;j++){
if(i>=0&&i<n&&j>=0&&j<n){
sum+=a[i][j];
num++;
}
}
}
int f=(sum+num-1)/num;
if(f<=t) res++;
//cout<<"("<<x<<","<<y<<")"<<f<<endl;
}
cout<<res;
return 0;
}
分析:
顺着题意来,没什么好说的,超时。
2、二维前缀和(100)
#include<iostream>
#include<cstring>
using namespace std;
int a[601][601];
int s[701][701];
int main(){
int n,l,r,t;
cin>>n>>l>>r>>t;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) cin>>a[i][j];
//cout<<endl;
for(int i=1;i<=n+2*r;i++){
for(int j=1;j<=n+2*r;j++){
int flag=1;
if(i<r||i>n+r||j<r||j>n+r) flag=0;//处在补零位置
s[i][j]=a[i-r][j-r]*flag;
s[i][j]=s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];//计算二维前缀和
//cout<<s[i][j]<<" ";
}
//cout<<endl;
}
//cout<<endl;
int res=0;
for(int i=r+1;i<=n+r;i++){//注意从非补零位置开始遍历
for(int j=r+1;j<=n+r;j++){
int num=(2*r+1)*(2*r+1);
int sum=s[i+r][j+r]-s[i+r][j-r-1]-s[i-r-1][j+r]+s[i-r-1][j-r-1];//求领域和
if(i-r<=r||j-r<=r||i+r>n+r||j+r>n+r){//领域内有补零位置
int a=2*r+1,b=2*r+1;//重新计算num
if(i-r<=r) a-=r+1-(i-r);//上边
if(j-r<=r) b-=r+1-(j-r);//左边
if(i>n) a-=i+r-(n+r);//下边
if(j>n) b-=j+r-(n+r);//右边
num=a*b;
}
int f=(sum+num-1)/num;//计算均值
//cout<<f<<" ";
if(f<=t) res++;
}
//cout<<endl;
}
cout<<res;
return 0;
}
分析:
灵光一闪想到了前缀和(ccf第二题真的太爱考前缀和了),对于领域不全的边界情况进行了补零,将原矩阵变成一个更大的矩阵,确保二维前缀和求区域和的公式中用到的值都有定义。对用到补零值的特殊领域重新计算了num。折磨了一个晚上,可算是把边界问题搞对了,也算是第一道满分第二题了(菜狗落泪)。
三、满分解法
#include<cstdio>
double a[601][601] = {0};
int main() {
int n, r;
double L, t;
scanf("%d %lf %d %lf", &n, &L, &r, &t);
//从下标1开始输入,即给矩阵外围补了一圈0,方便下面计算前缀和,无需考虑边界情况
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
scanf("%lf", &a[i][j]);
}
}
//求出每个点a[i][j]坐标为边界的i*j的矩阵内元素值的总和
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1];
}
}
int count = 0;
for(int i=1; i<=n; i++) {
//确定邻域的上下边界
int up = (i-r<1) ? 1 : (i-r);
int down = (i+r>n) ? n : (i+r);
for(int j=1; j<=n; j++) {
//确定邻域的左右边界
int right = (j+r>n) ? n : (j+r);
int left = (j-r<1) ? 1 : (j-r);
//求均值
double sum = a[down][right] - a[down][left-1] - a[up-1][right] + a[up-1][left-1];
double cr = (double)down-up+1;
double cl = (double)right-left+1;
double average = sum/(cr*cl);
if(average<=t) count++;
}
}
printf("%d", count);
return 0;
}
原文章:https://blog.csdn.net/weixin_49070253/article/details/123168339
分析:
大佬就是大佬,同样是二维前缀和补零,人家写得就很简洁灵活 。差距就在于他只补一圈零,边界跟 r 无关,更好分析。遍历到点再计算该点的领域边界,根据领域边界调整区域求和公式。