3.10前缀和与差分

3.10前缀和与差分

一维前缀和:一维数组区间求和

给定长度为 n 的序列 a[1],a[2]...a[n], 则 sum[i] = a[1]+a[2]...+a[i] = sum[i-1]+a[i].
区间和 [l,r]=sum[r]-sum[l-1]
image

【题目描述】
给定长度为 \(n\) 的序列 \(a[1],a[2],...,a[n]\),给定 \(q\) 个询问 \([l,r]\),输出 \([l,r]\) 的区间和。
数据范围: \(n,q \leq 10^5\)

输入格式:
第一行输入 \(n, q\) ;
第二行输入序列 \(a[1],a[2],...,a[n]\)
接下来 \(q\) 行,每行两个数据,分别对应 \(l, r\)

输出格式: 输出 \(q\) 行,表示 \([l, r]\) 区间和。

输入样例 输出样例
10  5
1 2 3 4 5 5 4 3 2 1
1 6
3 8
1 10
5 5
1 3
20
24
30
5
6

方法1:暴力计算每个询问 \([l,r]\),时间复杂度: \(O(q ×n)\)

方法2:前缀和,输出每个询问 \([l,r]\), 时间复杂度: \(O(n + q)\)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N], sum[N];

int main(){
    int n,m,l,r; scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++) scanf("%d", &a[i]);
    for(int i=1; i<=n; i++){ //前缀和预处理
        sum[i] = sum[i-1]+a[i];
    }
    for(int i=1; i<=m; i++){
        scanf("%d%d", &l, &r);
        printf("%d\n", sum[r]-sum[l-1]);
    }
    return 0;
}

二维前缀和:二维矩阵区间求和

给定一个矩阵 \(s[i][j]\)\(sum[i][j]\)是矩阵 \(s[1...i][1...j]\) 的和。

使用二维前缀和可以在 \(O(1)\) 时间内求一个矩阵和。
image

【题目描述】
给定一个 \(n*m\) 大小的矩阵 \(a\),有 \(q\) 次询问,每次询问给定 \(x1,y1,x2,y2\) 四个数,
求以 \((x1,y1)\) 为左上角坐标和 \((x2,y2)\) 为右下角坐标的子矩阵的所有元素和。
注意仍然包含左上角和右下角的元素。
数据范围: \(n,m,q<=10^4\)

输入格式:
第一行输入 \(n, m, q\) ;
接下来 \(n\) 行, 每行 \(m\) 个数据,表示矩阵元素;
接下来 $q $ 行,每行 \(4\) 个数据,分别对应 \(x1,y1,x2,y2\)

输出格式: 输出 \(q\) 行,表示以 \((x1,y1)\) 为左上角坐标和 \((x2,y2)\) 为右下角坐标的子矩阵的所有元素和。

输入样例输出样例
4 5 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 1 2 2
1 1 3 3
2 2 4 4
1 1 4 5
2 4 4 5
6
18
27
60
27
#include<iostream>
using namespace std;
const int N=1e4+10;
int a[N][N], sum[N][N];
int n,m,q,x1,y1,x2,y2,ans=0;

int main(){
    scanf("%d%d%d", &n, &m, &q);
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            scanf("%d", &a[i][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++){
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        ans = sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
        printf("%d\n", ans);
    }
    return 0;
}

一维差分:一维数组区间修改

差分:每个元素与前一个元素的差值
如原数组 arr: 1 2 3 4 5
则差分为 dif: 1 1 1 1 1  ( dif[] 的前缀和即为 arr[])

单点修改 arr[i]+add:
若将 arr[1]+1, 则 arr: 1 3 3 4 5
       则差分数组 dif: 1 2 0 1 1,
只会改变自己和后一个数, 即 dif[i] += add,  dif[i+1] -= add.

区间修改 [l,r] 增加 add:
若将 arr 数组 [2,4] 每个元素+1, arr: 1 3 4 5 5
                    则差分数组 dif: 1 2 1 1 0,
只会改变 dif[l] 和 dif[r] 后一个数,即 dif[l] += add, dif[r+1] -= add.

image

  • P2367 语文成绩
    【题目描述】
    给定长度为 \(n\) 的序列 \(a[1],a[2],...,a[n]\),初始都为 0,
    接着 \(m\) 个操作 \(l,r,add\) ,表示给 \(a\) 数组 \([l,r]\) 区间内的每个数加 \(add\)
    给定 \(q\) 个询问输出 \(a[i]\) 的值。
    数据范围: \(n,m,q≤1e5\)
输入样例 输出样例
10 5 10
1 2 1
2 2 2
2 2 3
5 8 4
2 10 5
1 2 3 4 5 6 7 8 9 10
1 11 5 5 9 9 9 9 5 5
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+10,INF=0x3f3f3f3f;
int n,m,q,a[N],b[N];

int main(){
    scanf("%d%d%d",&n,&m,&q); int l,r,add;
    while(m--){
        scanf("%d%d%d",&l,&r,&add);
        b[l] += add;
        b[r+1] -= add;
    }
    for(int i=1; i<=n; i++) b[i]=b[i-1]+b[i];
    while(q--){
        cin>>l;
        printf("%d ",b[l]);
    }
    return 0;
}

二维差分:二维矩阵区间修改

有一个二维矩阵

1 2 4 3
5 1 2 4
6 3 5 9

对应的二维差分矩阵如下:

1  1  2 -1
4 -5 -1  3
1  1  1  2

【题目描述】
输入一个 \(n\)\(n\) 列的整数矩阵,再输入 \(q\) 个操作,
每个操作包含五个整数 \(x1,y1,x2,y2,c\)
其中 \((x1, y1)\)\((x2, y2)\) 表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上 \(c\)
请你将进行完所有操作后的矩阵输出。

输入格式:
第一行包含整数 \(n,m,q\)
接下来 \(n\) 行,每行包含 \(m\) 个整数,表示整数矩阵。
接下来 \(q\) 行,每行包含 \(5\) 个整数 \(x1,y1,x2,y2,c\),表示一个操作。

输出格式:\(n\) 行,每行 \(m\) 个整数,表示所有操作进行完毕后的最终矩阵。

输入样例 输出样例
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
2 3 4 1
4 3 4 1
2 2 2 2

数据范围:

\[1 ≤ n, m ≤ 1000, \quad 1 ≤ q ≤ 100000,\\ 1 ≤ x1 ≤ x2 ≤ n, \quad 1 ≤ y1 ≤ y2 ≤ m,\\ 1000 ≤ c ≤ 1000, \quad 1000 ≤ 矩阵内元素的值 ≤ 1000 \]

#include<iostream>
using namespace std;
const int N=1e4+10;
int a[N][N], sum[N][N], dif[N][N];
int n,m,q,x1,y1,x2,y2,c;

int main(){
    scanf("%d%d%d", &n, &m, &q);
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            scanf("%d", &a[i][j]);
            dif[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1];
        }
    }
    for(int i=1; i<=q; i++){
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        dif[x1][y1] += c;
        dif[x1][y2+1] -= c;
        dif[x2+1][y1] -= c;
        dif[x2+1][y2+1] += c;
    }

    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            dif[i][j] += dif[i-1][j]+dif[i][j-1]-dif[i-1][j-1];
            printf("%3d", dif[i][j]);
        }printf("\n");
    }
    return 0;
}

【一篇很细节的差分讲解,请点击食用】

洛谷练习题目

三维差分

差分数组D[][][]是三维的
把三维差分想象成在立体空间上的操作。一维的区间是一个线段,二维是矩形,那么三维就是立体块。
一个小立体块有8个顶点,所以三维的区间修改,需要修改8个D[][][]值。

三维差分的区间修改

D[x1][y1][z1]       += d;  //前:左下顶点,即区间的起始点
D[x2+1][y1][z1]     -= d;  //前:右下顶点的右边一个点
D[x1][y1][z2+1]     -= d;  //前:左上顶点的上面一个点
D[x2+1][y1][z2+1]   += d;  //前:右上顶点的斜右上方一个点
D[x1][y2+1][z1]     -= d;  //后:左下顶点的后面一个点
D[x2+1][y2+1][z1]   += d;  //后:右下顶点的斜右后方一个点
D[x1][y2+1][z2+1]   += d;  //后:左上顶点的斜后上方一个点
D[x2+1][y2+1][z2+1] -= d;  //后:
posted @ 2021-12-25 23:46  HelloHeBin  阅读(340)  评论(0编辑  收藏  举报