算法_前缀和&差分

1.一维数组前缀和

一维数组的前缀和,就是这个数组从第m项到第n项的和(从1开始,默认索引为0的位置的值为0), 一般我们计算都是使用一个for循环,显然这样每次计算都会花费O(n)的时间复杂度.我们可以使用空间换时间,使用一个额外的数组,记录原数组中从第一项到每一项的和,然后在计算原数组第m到第n项和时只需要将1到n项的和减去1到(m-1)项的和即可,

原数列A  A1 A2 A3 ...An
使用Sn表示数列A的前n项的和 即 
S1 = A1 
S2 = A1+A2 
S3 = A1+A2+A3
Sn-1 = A1+A2+A3+...An-1
Sn = A1+A2+A3+...+An-1+An = Sn-1 + An

所以可以推出:
Sn = Sn-1 +An ①
---------------------------------------------------------------------------
我们现在要求A数列从 Am 到An的和
原始做法就是 Am+Am+1+...+An-1+An
我们引入了数列S后,计算Am到An的和  由于n大于等于m
Sm-1 = A1 + A2 +...+ Am-1 
Sn = A1 + A2 + ... +Am-1+Am+...+ An-1 +An
所以可以推出 Am到An的和 就等于 Sn - Sm-1 ②

例题

AcWing.795前缀和

import java.util.Scanner;
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] nums = new int[n+1];
        //s数组记录的就是数组nums中从第一项到第n项的和
        int[] s = new int[n+1];
        int m = scanner.nextInt();
        //从索引为1开始输入,0的位置默认是0
        for (int i = 1; i <= n ; i++) {
    
    
            nums[i] = scanner.nextInt();
        }
        //公式一   Sn = Sn-1 +An 求前缀和
        for (int j = 1; j <= n; j++) {
    
    
            s[j] = s[j - 1] + nums[j];
        }
        while (m-- > 0) {
    
    
            int l = scanner.nextInt();
            int r = scanner.nextInt();
            //公式二  l到r的和等于 Sr - Sl-1 
            int sum = s[r] - s[l - 1];
            System.out.println(sum);
        }
    }
}

2.二维数组前缀和

一个点A(i,j)(从1开始)的前缀和记为S(i,j),是以坐标为(i,j)的点坐标的左上角的区域的所有元素和

有下面的示意图可以得到

某一个点的前缀和
S(i,j)= = S(i-1,j) + S(i,j-1) - S(i-1,j-1) + A(i,j)

-----------------------------------------------------------------------------

点(x1,y1)到点(x2,y2)之间的前缀和,就是以(x1,y1)为左上角以(x2,y2)为右下角的矩阵的前缀和
 公式
Sum = S(x2,y2) - S(x1-1,y2) - S(x2,y1-1) + S(x1-1,y1-1)

示意图
在这里插入图片描述

例题

AcWing796.子矩阵的和

import java.util.Scanner;

public class Main {
    
    

    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int q = scanner.nextInt();
        int[][] A = new int[n + 1][m + 1];
        int[][] S = new int[n + 1][m + 1];

		//从索引为1开始赋值
        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= m; j++) {
    
    
                A[i][j] = scanner.nextInt();
            }
        }
        //计算每个位置的前缀和
        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= m; j++) {
    
    
                S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + A[i][j];
            }
        }

        while (q-- > 0) {
    
    
            int x1 = scanner.nextInt();
            int y1 = scanner.nextInt();
            int x2 = scanner.nextInt();
            int y2 = scanner.nextInt();
     //公式 计算某两个点的前缀和       
     int sum = S[x2][y2] - S[x1 - 1][y2] - S[x2][y1 - 1] + S[x1 - 1][y1 - 1];
            System.out.println(sum);
        }
    }
}

3.一维数组差分

差分是前缀和的逆运算比如说有一个数组A,它的前缀和数组是S,即S是A的前缀和数组,那么A就是S的差分数组

给定一个数组S如何求他的差分数组A

根据第一节的前缀和的推导,以及下面的推导,可以得到An = Sn - Sn-1

....
Sn-1 = Sn-2 + An-1 
Sn = Sn-1 + An

所以我们现在要根据Sn求出An 由上面的式子可以得到,An = Sn - Sn-1

具体题目

AcWing797.差分

给定一个数组,对于数组的某一段区间,都加上一个数,(操作m次)返回处理后的结果,

思路

  • 我们可以使用直接扫描的做法,扫描这个区间,给区间的数都加上指定的值,
  • 也可以使用差分的做法,先求出这个数组的差分数组,然后将差分数组的区间(比如从a到b区间)索引a上加上指定数,b+1索引上减去指定数,最后求出这个差分数组的前缀和数组就是答案
为什么这样做可以?
比如我们有一个数组是S
S1 = S0 + A1
S2 = S1 + A2
S3 = S2 + A3
...
Sl = Sl-1 + Al
....
Sr = Sr-1 + Ar
...
Sn-1 = Sn-2 + An-1
Sn = Sn-1 + An

-------------------------

它的差分数组A为
A1 = S1 - S0
A2 = S2 - S1
。。。。
Al = Sl - Sl-1
...
Ar = Sr - Sr-1
An-1 = Sn-1 - Sn-2
An = Sn - Sn-1

------------------------

所以,我们现在要将S数组中,从l位置到r位置上的是都加上C,那么我们先求出S的差分数组A,
我们将Al位置上的数+C,然后求出它的前缀和数组
A1 = S1 - S0;
...
Al = Sl - Sl-1 + C
...
An = Sn - Sn-1
      ||
      ||
   前缀和数组 
S1 = S0 + A1;
...
Sl = Sl-1 + Al = Sl + C
Sl+1 = Sl + Al+1 =Sl+1 + C 
....
Sn = Sn + C

所以我们发现,当Al+C后,它的前缀和数组从l到n都会加上C,但是我们现在只想让前缀和从l到r位置上的数加上C,根据上面的实验,我们A数组从r+1位置上的数全都减去C,那么S从r+1位置到n会减去C,所以我们就完成了前缀和数组的l到r位置上的数+C,其他位置的数不变的操作

代码实现

import java.util.Scanner;

public class Main {
    
    

    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[] S = new int[n + 2];
        int[] A = new int[n + 2];
        
        // 原数组 
        for (int i = 1; i <= n; i++) {
    
    
            S[i] = scanner.nextInt();
        }

        //先求他的差分数组
        for (int j = 1; j <= n; j++) {
    
    
            A[j] = S[j] - S[j - 1];
        }

        while (m-- > 0) {
    
    
            int l = scanner.nextInt();
            int r = scanner.nextInt();
            int c = scanner.nextInt();
            //差分数组的l位置+c
            A[l] += c;
            //差分数组的r位置-c
            A[r + 1] -= c;
        }
        //操作完成后求出查分数组的前缀和数组,这样就完成了原数组的l到r位置的操作
        for (int i = 1; i <= n; i++) {
    
    
            S[i] = S[i - 1] + A[i];
        }
        //输出
        for (int i = 1; i <= n; i++) {
    
    
            System.out.print(S[i] + " ");
        }
    }
}

4.二维数组差分

二维数组的差分跟一维数组类似,都是前缀和的逆运算。根据求点(i,j)前缀和的公式

S(i,j)= = S(i-1,j) + S(i,j-1) - S(i-1,j-1) + A(i,j)

可以得到一个数组的某个点经过差分后运算后的结果

 A(i,j) = S(i,j) - S(i-1,j) - S(i,j-1) + S(i-1,j-1)

核心公式

求出差分数组后对(x1,y1)(x2,y2)的操作就是下面的四步
在这里插入图片描述

例题

AcWing798.差分矩阵

跟上面的差分那道题类似,将二维数组的某一个子矩阵都加上某个数C,这里也可以先将原数组化为差分矩阵,然后可以得出差分矩阵的某个点加上或者减去C对于它的前缀和数组的影响,即可解决。

import java.util.Scanner;

public class Main {
    
    

    public static void main(String[] args) {
    
    

        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[][] S = new int[n + 2][m + 2];
        int[][] A = new int[n + 2][m + 2];
        int q = scanner.nextInt();

        //原数组
        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= m; j++) {
    
    
                S[i][j] = scanner.nextInt();
            }
        }

        //求其差分数组
        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= m; j++) {
    
    
                A[i][j] = S[i][j] - S[i - 1][j] - S[i][j - 1] + S[i - 1][j - 1];
            }
        }

        //公式 修改差分数组指定位置的元素
        while (q-- > 0) {
    
    
            int x1 = scanner.nextInt();
            int y1 = scanner.nextInt();
            int x2 = scanner.nextInt();
            int y2 = scanner.nextInt();
            int C = scanner.nextInt();
			//核心就是这里 改变差分数组的一些值
            A[x1][y1] += C;
            A[x1][y2 + 1] -= C;
            A[x2 + 1][y1] -= C;
            A[x2 + 1][y2 + 1] += C;
        }

        //构造前缀和数组
        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= m; j++) {
    
    
                S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + A[i][j];
            }
        }

        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= m; j++) {
    
    
                System.out.print(S[i][j]+ " ");
            }
             System.out.println();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_46312987/article/details/119718257
今日推荐