【模板】B000_JM_子矩阵的和、差分(二维前缀和 / 一维差分)

一、子矩阵的和

输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角座标和右下角座标。
对于每个询问输出子矩阵中所有数的和。

Input

第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。

Output

共 q 行,每行输出一个询问的结果。

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

17
27
21

数据范围

1 ≤ n, m ≤ 1000,
1 ≤ q ≤ 200000,
1 ≤ x1 ≤ x2 ≤ n,
1 ≤ y1 ≤ y2 ≤ m,
−1000 ≤ 矩阵内元素的值 ≤ 1000


Q:二维前缀和怎么求?
在这里插入图片描述
A:以上面的表格为例,假如我要求 a[2][4]a[2][4] 的前缀和:

  • 先加上 a[1][4]a[1][4] 的前缀和,再加上 a[2][3]a[2][3] 的前缀和
  • 我们发现 a[1][3]a[1][3] 这个部分我们加了两遍,所以我们需要再减去一遍 a[1][3]a[1][3]
  • 于是得出公式 a[i][j] += a[i][j1]+a[i1][j]a[i1][j1]a[i][j]\ +=\ a[i][j-1]+a[i-1][j]-a[i-1][j-1]

其实 a[1][4]a[1][4] 的前缀和 a[2][3]a[2][3] 的前缀和也就是一维前缀和

方法一:二维前缀和

Q:如何求子矩阵的和,比如深蓝色区域的子矩阵的和?
在这里插入图片描述
我们每次要求的答案就是红色圆圈所在的区域的值,对比上面这张图我们能够发现红色区域的前缀和的值等于四个区域的前缀和的值减去(红色区域+绿色区域),再减去(红色区域+浅蓝色区域),最后因为红色区域隐式地被减了两次,我们需要再加一次回来。所以 ans 可得:

ans=a[x2][y2]a[x11][y2]a[x2][y11]+a[x11][y11]ans=a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]

import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
	static class Solution {
		void init() {
			Scanner sc = new Scanner(new BufferedInputStream(System.in));
			int r = sc.nextInt(), c = sc.nextInt(), q = sc.nextInt();
			long a[][] = new long[r+1][c+1];
			for (int i = 1; i <= r; i++)
			for (int j = 1; j <= c; j++) {
				a[i][j] = sc.nextInt();
			}
			for (int i = 1; i <= r; i++)
			for (int j = 1; j <= c; j++) {
				a[i][j] += (long) (a[i][j-1] + a[i-1][j] - a[i-1][j-1]);
			}

			while (q-- > 0) {
				int x1 = sc.nextInt(),y1 = sc.nextInt(),x2 = sc.nextInt(),y2 = sc.nextInt();
				long ans = (long) (a[x2][y2] - a[x1-1][y2] - a[x2][y1-1] + a[x1-1][y1-1]);
				System.out.println(ans);
			}
		}
	}
    public static void main(String[] args) throws IOException {  
        Solution s = new Solution();
		s.init();
    }
}

复杂度分析

  • 时间复杂度:O(R×C)O(R × C)
  • 空间复杂度:O(R×C)O(R × C)

二、差分模板题

你需要维护一个长度为 N 的序列,支持 Q 次区间修改,每次将区间 [l,r][l,r] 内的数字加 val,最后输出修改之后的序列。

Input

第一行两个整数 N,Q.
第二行 N 个整数,代表序列初始值.
第三行开始连续 Q 行,每行三个整数 l,r,val.

Output

一行输出 N 个数字,代表最后的序列.

Sample Input
5 2
1 2 3 4 5
1 3 1
4 5 1

Sample Output
2 3 4 5 6

Hint
0<=N,Q<=100000

方法一:差分

暴力做法是去枚举区间,时间复杂度为 O(NQ)O(NQ) 会超时;线段树需要 O(QlogN)O(QlogN);差分只需要 O(Q)O(Q)

在这里插入图片描述

这里的数组 d 就是数组 a 的差分数组,对差分数组求一遍前缀和运算,就可以得到原数组 a;可是这个数组 d 有什么用?这里先放一下,假设我对 a 数组的区间 [2,4][2, 4] 的数都加上 5,重新算一下 d 数组看会发生什么?
在这里插入图片描述
对比一下,发现只有 d[2]d[2]d[5]d[5] 发生了改变(都减去了 5),那反过来,假如我要想给数组 aa 的区间 [l,r][l, r] 都加上 kk,那么 d 数组变化的只有 d[l]d[l]d[r+1]d[r+1]

所以可以推出:如果要让数组 a 的区间 [l,r][l,r] 的数都加上 k,我们只用让 d[l]d[l] 加上 kk,然后让 d[r+1]d[r+1] 减去 kk,然后对 d 数组的 [l,r][l, r] 求一遍前缀和即可求出修改后的 a 数组。

Q:这里为什么要对 b[r+1] 减去 k 呢?

A:首先你要明确 d[i]=a[i]a[i1]d[i] = a[i] - a[i-1],所以 d[l]d[l] 加上了 kk,那么数组 d 在区间 [l,n][l, n] 中的数都会加上 kk,而我们只需要将 [l,r][l,r] 的数加上 kk,所以 r+1r+1 之后的数被 “莫名其妙” 地加上了 kk,怎么才能让 [r+1,n][r+1, n] 的数都减去 k 呢,没错,d[k+1]=kd[k+1] -= k

import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
    static class Solution {
        void init() {
            Scanner sc = new Scanner(new BufferedInputStream(System.in));
            int n = sc.nextInt(), q = sc.nextInt(), a[] = new int[n+1], b[] = new int[n+2];

            for (int i = 1; i <= n; i++) {
                a[i] = sc.nextInt();
                b[i] = a[i] - a[i-1];
            }
            for (int i = 0; i < q; i++) {
                int l = sc.nextInt(), r = sc.nextInt(), k = sc.nextInt();
                b[l] += k;   b[r+1] -= k;
            }
            int[] s = Arrays.copyOf(b, b.length);
            for (int i = 1; i <= n; i++) {
                s[i] = s[i] + s[i-1];
                System.out.print(s[i] + " ");
            }
        }
    }
    public static void main(String[] args) throws IOException {  
        Solution s = new Solution();
        s.init();
    }
}

复杂度分析

  • 时间复杂度:O(max(N,Q))O(max(N, Q))
  • 空间复杂度:O(n)O(n)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章