前缀和与差分(上)

前言

前缀和与差分是算法优化的重要思想,由于篇幅较长,所以分为上下两集,本文先介绍前缀和的相关部分,下集再介绍差分部分。

一、前缀和是什么?

前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和。
例:某序列为a[1]、a[2]、a[3]、a[4]、a[5]
则前3项和为S[3] = a[2] + a[2] + a[3].

二、前缀和

作用
降低算法的复杂度,简化计算。

原理:若求原序列的子序列和可通过前r项和减去前(l-1)项和

数学公式:a[l]+a[l+1]+…+a[r] = sum[r] - sum[l-1]

首先看一下这个问题:
输入一个长度为n的整数序列。接下来再输入m个询问,每个询问输入一对l, r。对于每个询问,输出原序列中从第l个数到第r个数的和。
我们很容易想出暴力解法,遍历区间求和。

代码:

#include <iostream>
using namespace std;

int main(){
    int a[5] = {1,2,3,4,5};
    int m,l,r;//询问次数、left边界、right边界
    cin>>m;
    for(int i = 1;i <= m;i++){
        int sum=0;
        cin>>l>>r;
        for(int i = l; i <= r;i++){
            sum += a[i];
        }
        std::cout << sum << std::endl;
    }
    return 0;
}
// 测试输入
// 3
// 1 4
// 2 3
// 1 3

// 测试结果
// 14
// 7
// 9



这里的复杂度是o(m * n)(n为数组a长度、m为询问次数),如果m与n较大算法所需的时间较长就有可能计算超时,使用前缀和可将o(m * n)的复杂度降为o(m + n).

前缀和的具体做法:
首先定义一个存放前缀和数组sum[]、原序列a[], 其中sum[i]是通过a[1] + a[2] + …+ a[i]累加后的结果.

代码:

#include <iostream>
using namespace std;

int main(){
    int a[6] = {1,2,3,4,5,0};
    int sum[6];
    for(int i = 1;i <= 6;i++){
        sum[i] = sum[i-1] + a[i]; 
    }
    
    int m,l,r;//询问次数、left边界、right边界
    cin>>m;
    for(int i = 1;i <= m;i++){
        cin>>l>>r;
        std::cout << sum[r] - sum[l-1] << std::endl;
    }
    return 0;
}
// 测试输入
// 3
// 1 4
// 2 3
// 1 3

// 测试结果
// 14
// 7
// 9

求前缀和sum[]数组运算算法度为o(n)(n为数组a[]长度)、sum[]数组查询复杂度为o(1),由于求前缀和与询问次数是并列for,所以总复杂度为:O = O(n+m).

练习题:
输入一个长度为n的整数序列。
接下来再输入m个询问,每个询问输入一对l, r。
对于每个询问,输出原序列中从第l个数到第r个数的和。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式
共m行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
代码:

// 令a[0]、s[0]为零,方便边界处理
// a[N]、sum[N]也可以声明为全局变量,赋值N = 100010(比题目要求的范围大一点)
#include <iostream>
using namespace std;

int main(){
    int n,m,l,r;//数组长度、询问次数、left边界、right边界
    cin>>n>>m;
    int a[n+1]; //从1开始赋值,方便理解
    int sum[n];
    for(int i = 1;i <= n;i++){
        cin>>a[i];
    }
    for(int i = 1;i <= n;i++){
        sum[i] = sum[i-1] + a[i]; 
    }
    
    for(int i = 1;i <= m;i++){
        cin>>l>>r;
        std::cout << sum[r] - sum[l-1] << std::endl;
    }
    return 0;
}


三、二维前缀和

二维前缀和公式s[i] [j] = s[i-1][j] + s[i][j-1 ] + a[i] [j] - s[i-1][ j-1] (2^2 = 4,4个相加)

二维子面积和公式s[x2-x1][y2-y1] = s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1](以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和)

**几何直观理解:

点击放大image.png

引用–更详细内容请参考博客

练习一道完整题目:
输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。

对于每个询问输出子矩阵中所有数的和。

输入格式
第一行包含三个整数n,m,q。

接下来n行,每行包含m个整数,表示整数矩阵。

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

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

数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例:
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

代码:

#include <iostream>
using namespace std;

int main(){
    int n,m,q;//行、列
    int x1,y1,x2,y2;
    cin>>n>>m>>q;
    int a[n+1][m+1]; //从1开始赋值,方便理解
    int s[n+1][m+1]; 
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <=m;j++){
            cin>>a[i][j];
        }
    }
    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 <= q;i++){
        cin>>x1>>y1>>x2>>y2;
        std::cout << s[x2][y2] - s[x1 -1][y2] - s[x2][y1-1] + s[x1-1][y1-1]<< std::endl;
    }
    return 0;
}


四、三维前缀和

三维前缀和公式:2^3 = 8,8个相加
S(X, Y, Z) = a(X, Y, Z)
+S(X, Y, Z - 1) + S(X, Y - 1, Z) + S(X - 1, Y, Z)
-S(X, Y - 1, Z - 1) - S(X - 1, Y, Z - 1) - S(X - 1, Y - 1, Z)
+S(X - 1, Y - 1, Z - 1)

方便记忆
S(X,Y,Z)括号里面的"-"位置由二进制的"1"位置决定
加减运算符号由二进制总数"1"决定,若为奇数就(+)、为偶数就(-)

S(X, Y, Z) = a(X, Y, Z) + 表示
001 符号:+ S(X, Y, Z - 1)
010 符号:+ S(X, Y - 1, Z)
011 符号:- S(X, Y - 1, Z - 1)
100 符号:+ S(X - 1, Y, Z)
101 符号:- S(X - 1, Y, Z - 1)
110 符号:- S(X - 1, Y - 1, Z)
111 符号:+ S(X - 1, Y - 1, Z - 1)

五、总结

一维前缀和公式:S[i] = a[1] + a[2] + a[3] + …+ a[i].

一维子序列和公式:a[l]+a[l+1]+…+a[r] = sum[r] - sum[l-1]

二维前缀和公式s[i] [j] = s[i-1][j] + s[i][j-1 ] + a[i] [j] - s[i-1][ j-1]

二维子面积和公式s[x2-x1][y2-y1] = s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1](以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和)
三维前缀和公式
S(X, Y, Z) = a(X, Y, Z)
+S(X, Y, Z - 1) + S(X, Y - 1, Z) + S(X - 1, Y, Z)
-S(X, Y - 1, Z - 1) - S(X - 1, Y, Z - 1) - S(X - 1, Y - 1, Z)
+S(X - 1, Y - 1, Z - 1)

几何直观理解:可以将a[i]看做点,sum[i]为长度、s[i][j]为面积,s(x,y,z)为体积

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值