前缀和&&差分

前缀和&&差分

​ 这算两个小技巧吧,下面就给大家介绍一下他们的用处。

前缀和

一维前缀和

题意:

一个长度为 n 的数组a,有 q 次询问, 每次询问有两个数 l r,问数组中区间 [l,r]之间的数之和。

示例:

输入:
第一行两个数,分别是 n 和 q, 
第二行是 n 个数, 代表数组 a,
接下来是 q 行, 每行两个数 l r, 
5 3
1 2 3 4 5
1 2
2 2 
1 5
输出:
3
2
15

定义一个数组 sum[i] , 代表前 i 个数之和,如果想求区间 [l, r] 的数之和,直接可以由 sum[r] - sum[l-1] 得到。即前 r 个数之和减去前 l - 1 个数之和,就是区间的数之和。

public static void main(String[] args) {
    Scanner cin = new Scanner(System.in);
    int n,q,l,r;
    n = cin.nextInt(); // 读入
    q = cin.nextInt(); // 读入
    int[] a = new int[n+1];
    int[] sum = new int[n+1];

    for (int i = 1; i <= n; ++i)
        a[i] = cin.nextInt();  // 读入数组
    sum[0] = 0;
    for (int i = 1; i <= n; ++i)  // 计算前缀和
        sum[i] = sum[i-1] + a[i];
    for (int i = 1; i <= q; ++i){
        l = cin.nextInt(); r = cin.nextInt();
        System.out.println(sum[r] - sum[l-1]); // 输出答案
    }
}
二维前缀和

题意:

有 n 行 m 列的二维数组 a,每次询问给定四个数字 x1 y1 x2 y2, 分别代表矩形的左上角和右下角,求矩形内的数字和。

示例:

输入:
第一行 n m q, 
接下来 n 行, 每行 m 个数字, 
接下来 q 行, 每行四个数字的询问。
4 4 2
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
1 1 2 2 
输出:
1
4

定义一个数组 sum[i][j] , 代表 0,0为左上角 i,j为右下角的矩形数字和,如果想求 x1 y1 x2 y2 矩形的数之和,直接可以由 sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1] 得到。
image-20201119153858076

由上图可知, 如果我们想得到红色的部分, 就要拿 天蓝色 - 黄色 - 粉红色 + 青色

具体我们看一下代码:

public class Solution {
    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        int n,m,q,x1,x2,y1,y2;
        n = cin.nextInt(); // 读入
        m = cin.nextInt();
        q = cin.nextInt(); // 读入
        int[][] a = new int[n+1][m+1];
        int[][] sum = new int[n+1][m+1];

        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j)
                a[i][j] = cin.nextInt();

        for (int i = 1; i <= n; ++i)  // 计算前缀和
            for (int j =1; j <= m; ++j)
                sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j];  // 这个可以自己画图理解一下。
        for (int i = 1; i <= q; ++i){
            x1 = cin.nextInt(); y1 = cin.nextInt();
            x2 = cin.nextInt(); y2 = cin.nextInt();
            System.out.println(sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1]); // 输出答案
        }
    }
}

差分

题意:

有一个长度为 n 的数组 a, 一开始数组所有的值都是 0, 有 m 次操作,每次操作有三个数字 l r w, 即给区间[l,r] 内所有的值都加上 w, 最后输出 a 数组。

示例:

输入:
第一行 两个数字 n m
下面 m 行, 每行三个数字 l r w
5 3
1 1 1
1 3 4
3 5 2

输出:
5 4 6 2 2

很暴力的做法:

​ 每次的操作, for 循环从 l 到 r, 对于其中的每个值直接加上 w 就可以了。

差分的做法:

​ 每次的操作, a[l] += w, a[r+1] += -w, 最后来一遍 for 循环做个前缀和, 就是想要的答案了。

我们来想一下,差分的做法为什么对呢!!!

在 l 的位置上 +w, 在 r+1 的位置 + (-w), 这样做前缀和的时候,w + (-w) = 0, 这样 w 就只会影响区间 [l, r] 里面的位置。
举个例子:

举个例子

3 6 5 // [3 6] 加上5
1  2  3  4  5  6  7  8  // 下标
0  0  5  0  0  0 -5  0  // 对应位置加减 5
0  0  5  5  5  5  0  0  // 前缀和

我们可以看到对于区间 [3, 6] 加上 5 这个操作,差分 + 前缀和是正确的。
可以在深入的想一下,每个区间的加操作是独立的,最后只做一遍前缀和。那么最后一遍的前缀和就可以保证每次区间加操作的正确。

再看个例子:

3 6 5  //[3 6] 加上 3
5 9 2  //[5 9] 加上 2    

图片第二行为前缀和

image-20201119162046037

public class Solution {

    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        int n,m,l,r,w;
        n = cin.nextInt(); // 读入
        m = cin.nextInt();
        int[] a = new int[n+10];

        for (int i = 1; i <= m; ++i) {
            l = cin.nextInt();
            r = cin.nextInt();
            w = cin.nextInt();
            a[l] += w;
            a[r+1] -= w;
        }
        for (int i = 1; i <= n; ++i)
            a[i] = a[i-1] + a[i];  // 在同一个数组中计算前缀和
        
        for (int i = 1; i <= n; ++i)
            System.out.print(a[i] + " ");
        System.out.println();
    }
}

总结

前缀和 && 差分 都是解题的思想,大家了解一下就可以了。

有些地方用这些方法可以有意想不到的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值