2021-4-2领域均值(二维前缀和两种补零方法)(c/c++实测满分)

总结:

        在传统二维前缀和求解的思路上对领域边界进行了要求。我的方法是根据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 无关,更好分析。遍历到点再计算该点的领域边界,根据领域边界调整区域求和公式

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值