二维前缀和
题目链接:www.nowcoder.com/practice/99…
题目描述
给你一个 n 行 m 列的矩阵 A ,下标从1开始。
接下来有 q 次查询,每次查询输入 4 个参数 x1 , y1 , x2 , y2
请输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和,
输入描述:
第一行包含三个整数n,m,q.
接下来n行,每行m个整数,代表矩阵的元素
接下来q行,每行4个整数1,1, 2, 2,分别代表这次查询的参数
5
99
1≤2
输出描述:
输出q行,每行表示查询结果。
示例1
输入:
3 4 3
1 2 3 4
3 2 1 0
1 5 7 8
1 1 2 2
1 1 3 3
1 2 3 4
复制
输出:
8
25
32
复制
备注:
读入数据可能很大,请注意读写时间。
解题
先看题,题目中第一行输入的前两位是二维数组的行和列,第三位数是要查询的次数
而且和一维数组不一样的是,二维数组求的是矩阵的和,也就是一个长方形的和,并不是从某一个位置到某一个位置的区间和。
解这题要是能联想到数学中的求面积就能做出来,我们直接画图
假设给出的是 4 × 4的二维数组,要查询的是11 32
这个矩阵的值,所以图应该这样画
绿色部分就是这个矩阵,而且返回的就是这个矩阵中所有数之和,那么我们和一维前缀和一样,创建一个二维数组dp
,用它来记录从
00 ij
这个矩阵的和,如果题目要求的是中间部分的矩阵,我们在减去其他部分,例如上图,我们先求出00 32
这个矩阵的和,记录到dp[3][2]
中,在减去dp[3][0]
这块矩阵就好了。
但如果要求的矩阵如下图绿色部分
它所处的位置是中间,那么就需要减去上部分和左部分,这样就会减2次dp[0][0]
,所以要再加上一个dp[0][0]
。
那还需要判断是需要减一次还是减两次吗?岂不是很繁琐,而且你还没说求和到底怎么求呢(求和部分在下面)。 并不需要,和一维数组一样,题目中的下标是从1
开始的,如果我们创建二维数组的时候把范围多加1
,然后赋值下标从1
开始,就会这样的效果,如下图
下标从1开始的话,以
0
下标为行和列的部分就默认初始化为0
了,这样我们无论在求和还是减去多余的部分的时候,都不需要判断是否为边界,因为0对于数值加减没有影响,如下图。
当下标从1开始之后,绿色部分就为边界,仍然可以用11 32
部分减去上部分和左部分再加上重复的部分。
那下面就说说如何求得dp[i][j]
方法是相同的,假设我们要求得dp[1][2]
,如下图。那么我们就可以橙色区域加上绿色区域,然后再减去重复相加的部分,也就是橙色绿色混合区域,再加上arr[1][2]
本身的值。这样就得出dp[1][2]
。
剩下的就是将区域转换成坐标了,绿色部分的值其实就是dp[0][2]
,橙色部分就是dp[1][1]
,重复部分就是dp[0][1]
。
所以得出公式:
求dp[i][j]
:
求最终值result
:1111
代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
//输入行、列、查询次数
int n = in.nextInt();
int m = in.nextInt();
int q = in.nextInt();
//创建数组
int[][] arr = new int[n + 1][m + 1];
long[][] dp = new long[n + 1][m + 1];
//下标从1开始
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
//先赋值
arr[i][j] = in.nextInt();
//同时求得dp[i][j],放到一个循环里就不需要再额外遍历一次
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 1];
}
}
for (int i = 0; i < q; i++) {
//输入矩阵坐标
int x = in.nextInt();
int y = in.nextInt();
int x1 = in.nextInt();
int y1 = in.nextInt();
//根据区域减法求得result
long result = dp[x1][y1] + dp[x - 1][y - 1] - dp[x - 1][y1] - dp[x1][y - 1];
System.out.println(result);
}
}
}