样例输入
4 16 1 6
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
样例输出
7
样例输入
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
样例输出
83
评测用例规模与约定
在看到这个题后,想着暴力能做出来,但是,觉得太麻烦,就不想用暴力做,发现暴力还过不了,果断放弃
这里先讲一下题目的大概意思,就是题目会先输入四个数据,n表示n*n的矩阵,后面就会输入矩阵,l表示矩阵中的元素最大不会超过l,r表示某个元素半径为r的范围内,t表示在半径为r的范围内的平均值要是小于t就表明这个元素是符合要求的,最后就是要输出符合要求的点的个数。
很明显我们需要求某个元素半径为r内的元素之和和元素个数,这个时候就要用到前缀和,通过前缀和来确定目标区域的和。
这个题目可以用一维的前缀和,也可以用二维的前缀和,来说说他们有什么区别。一维的前缀和就是一行一行求,它的sum数组里的每个值只代表这一行里,这个元素和他前面的元素的和,二维数组就是不仅求了行的和,也要求列的和。
下面是一维数组前缀和的代码,一维前缀和就需要额外判断下第一个元素
public class Main {
public static void main(String[] args) {
int[] sum=new int[5];
int[] arr={1,2,3,4};
for(int i=0;i<4;i++) {
if(i==0)
sum[i]=arr[i];
else sum[i]=sum[i-1]+arr[i];
}
for(int i=0;i<4;i++) {
System.out.println(sum[i]);
}
}
}
也有另一种方法算一维前缀和,也是空间换取时间的做法
public NumMatrix(int[][] matrix) {
int m = matrix.length;
if (m > 0) {
int n = matrix[0].length;
sums = new int[m][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
sums[i][j + 1] = sums[i][j] + matrix[i][j];
}
}
}
}
可能有的人不太理解二维前缀和,我来举个栗子,就拿样例1来说吧,假设这个数组是arr
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
就从下标为1开始算吧
sum[1][1]=1
sum[1][2]=1+2=sum[1][1]+arr[1][2]=3…
sum[2][1]=1+5=6
sum[2][2]=1+2+5+6=sum[1][2]+sum[2][1]-sum[1][1]+arr[2][2]=14
sum[2][3]=1+2+3+5+6+7=sum[1][3]+sum[2][2]-sum[1][2]+arr[2][3]=6+14-3=17
…
通过上边的例子
我们可以看出来,在第一行的时候其实就是sum[i][j-1]+arr[i][j]
第一列就是sum[i-1][j]+arr[i[j]
在其他情况下就是sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+arr[i][j],可以这样求出前缀和
接下来看代码
因为第一行,第一列处理的方法不一样,所以就直接单拉出来
public class Main {
public static void main(String[] args) {
int[][] arr={{1,1,1,1},
{1,1,1,1},
{1,1,1,1},
{1,1,1,1}};
int[][] sum=new int[4][4];
sum[0][0]=arr[0][0];
for(int i=1;i<4;i++) {
sum[i][0]=sum[i-1][0]+arr[i][0];//第一列
sum[0][i]=sum[0][i-1]+arr[0][i];//第一行
}
for(int i=1;i<4;i++) {
for(int j=1;j<4;j++) {
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+arr[i][j];
}
}
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
System.out.print(sum[i][j]+" ");
}
System.out.println("");
}
}
}
还有另一种方法算二维前缀和,是用空间换时间的做法,但是能一次性直接算出整个数组的前缀和
public NumMatrix(int[][] matrix) {
int m = matrix.length;
if (m > 0) {
int n = matrix[0].length;
sums = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
sums[i + 1][j + 1] = sums[i][j + 1] + sums[i + 1][j] - sums[i][j] + matrix[i][j];
}
}
}
}
接下来就是看这道题,我这篇题解是用的一维前缀和来求的,我觉得这样做起来好处就是不会乱,比较好理解
还是按照例一来讲解
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
用一维数组求得的前缀和是
0 1 3 6
4 9 15 22
8 17 27 38
12 25 39 54
他们每行之间只是列有关系,行之间没有求和关系
知道了原理,现在就来看看代码
public class Main {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int L=sc.nextInt();
int r=sc.nextInt();
int t=sc.nextInt();
int elem=0;
int res=0;
int[][] sum=new int[n+1][n+1];
int[][] arr=new int[n+1][n+1];
//求得前缀和
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
arr[i][j]=sc.nextInt();
//先求出来前缀和。,这里的前缀和是一维前缀和
if(j==1)
sum[i][j]=arr[i][j];
else
sum[i][j]=arr[i][j]+sum[i][j-1];
}
}
int bx,by,ex,ey,cnt=0;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
//看有没有出界,表示正方形的上下左右
bx=Math.max(i-r,1);//上边
by=Math.max(j-r,1);//左边
ex=Math.min(i+r,n);//下边
ey=Math.min(j+r,n);//右边
int ans=0;//用来判断正方形里元素的和
//从上边到下边开始遍历
for(int k=bx;k<=ex;k++) {
if(by==1)
ans+=sum[k][ey];
else ans+=sum[k][ey]-sum[k][by-1];
}
int num=(ex-bx+1)*(ey-by+1);//计算矩形里有多少个元素
if(ans*1.0/num<=t)
cnt++;
}
}
System.out.println(cnt);
}
}
这个题要从头开始遍历,所以有的元素会越界,所以就需要判断下越界的情况,如果越界,这个矩形相应的边就变成了0,所以需要判断边界情况,bx就是beginx,ex就是endx
bx=Math.max(i-r,1);//上边
by=Math.max(j-r,1);//左边
ex=Math.min(i+r,n);//下边
ey=Math.min(j+r,n);//右边
这四句话就把这个矩形的范围给定下来了,i-r确定的是上边界,如果越界了,上边界就是0,否则就是i-r,后面的同理
for(int k=bx;k<=ex;k++) {
if(by==1)
ans+=sum[k][ey];
else ans+=sum[k][ey]-sum[k][by-1];
}
int num=(ex-bx+1)*(ey-by+1);//计算正方形里有多少个元素
if(ans*1.0/num<=t)
cnt++;
}
如图所示,如果by==1,说明左边全部都得要,此时它的和就是
sum[1][2]+sum[2][2],因为sum[1][2]表示的是第一行中前两个数字的和,sum[2][2]表示的是第二行中前两个数字的和,他们加起来就是前两行中前两个数字的和,就是左上角四个数字的和。
当by!=1的时候
这样就能完成求和运算。
接下来就是平均值,要求平均值就得知道元素个数,我们已经知道了左边界,右边界,上边界,下边界,根据他们就能确定矩形的大小
int num=(ex-bx+1)*(ey-by+1);//计算矩形里有多少个元素
最后求平均,比较,就能得出答案。
总结
本题的考点在于前缀和,但是需要选择是二维前缀和还是一维前缀和,经过两个比较来看,我觉得一维的前缀和将元素之间的和分开,没有那么多的重复计算,这样更能简单,清楚地算出需要地和,所以我觉得这里用一维前缀和会更加方便一些。