3.10前缀和与差分


3.10前缀和与差分

目录
  • 3.10前缀和与差分
    • 一维前缀和:一维数组区间求和
    • 二维前缀和:二维矩阵区间求和
    • 一维差分:一维数组区间修改
    • 二维差分:二维矩阵区间修改
    • 洛谷练习题目

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

给定长度为 \(n\) 的序列 \(a[1],a[2]...a[n]\), 则 \(sum[i] = a[1]+...+a[i] = sum[i-1]+a[i]\).

【题目描述】
给定长度为 \(n(n<=1e5)\) 的序列 \(a[1],a[2],...,a[n]\),
给定 \(q (q<=1e5)\) 个询问 \([l,r]\),输出 \([l,r]\) 的区间和。

【输入格式】
第一行输入 \(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
using namespace std;

const int N=1e5+10;
int a[N], sum[N];

int main(){
    freopen("data.in", "r", stdin);
    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];
    }
//    printf("  下标:"); for(int i=1; i<=n; i++) printf("%3d", i); printf("\n");
//    printf("原数组:"); for(int i=1; i<=n; i++) printf("%3d", a[i]); printf("\n");
//    printf("前缀和:"); for(int i=1; i<=n; i++) printf("%3d", sum[i]);printf("\n\n");

    for(int i=1; i<=m; i++){
        scanf("%d%d", &l, &r);
        printf("l=%2d r=%2d sum[L~R]=%d\n", l, r, sum[r]-sum[l-1]);
    }
    return 0;
}

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

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

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

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

【输入格式】
第一行输入 \(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
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;
void print();

int main(){
    freopen("data.in", "r", stdin);
    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];
        }
    }
//    print();// 打印输出

    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);
//        printf("(%d,%d)->(%d,%d): ans=%d\n", x1, y1, x2, y2, ans);
    }
    return 0;
}

void print(){
    printf("原数组:\n");
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++) printf("%3d", a[i][j]);
        printf("\n");
    }printf("\n");

    printf("二维前缀和数组:\n");
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++) printf("%3d", sum[i][j]);
        printf("\n");
    }printf("\n");
}

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

差分:每个元素与前一个元素的差值
如原数组 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.

【题目描述】
给定长度为 \(n(n<=1e5)\) 的序列 \(a[1],a[2],...,a[n]\),初始都为 0,
接着 \(m(m<=1e5)\) 个操作 \(l,r,add\) ,表示给 \(a\) 数组 \([l,r]\) 区间内的每个数加 \(add\)
给定 \(q(q<=1e5)\) 个询问输出 \(a[i]\) 的值。

输入样例:

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 10 -6  0  4  0  0  0 -4  0
#include
using namespace std;

const int N=1e5+10;
int a[N], sum[N], dif[N];
int n,m,q,l,r,add,id;
void print();

int main(){
    freopen("data.in", "r", stdin);
    scanf("%d%d%d", &n, &m, &q);
    for(int i=1; i<=m; i++){
        scanf("%d%d%d", &l, &r, &add);
        dif[l] += add;
        dif[r+1] -= add;
    }
    for(int i=1; i<=n; i++){
        sum[i] =sum[i-1]+dif[i];
    }
    print();// 打印输出

    for(int i=1; i<=q; i++){
        scanf("%d", &id);
        printf("%3d", sum[id]-sum[id-1]);
    }
    return 0;
}

void print(){
    printf(" 下标:\n");
    for(int i=1; i<=n; i++) printf("%3d", i); printf("\n");

    printf("差分数组:\n");
    for(int i=1; i<=n; i++) printf("%3d", dif[i]); printf("\n");

    printf("前缀和数组:\n");
    for(int i=1; i<=n; i++) printf("%3d", sum[i]); printf("\n");

    printf("原数据数组:\n");
    for(int i=1; i<=n; i++) printf("%3d", sum[i]-sum[i-1]); printf("\n");
}

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

有一个二维矩阵

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
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(){
    freopen("data.in", "r", stdin);
    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;
}

洛谷练习题目

  • P5638 【CSGRound2】光骓者的荣耀
  • P1115 最大子段和
  • P3397 地毯
  • P1719 最大加权矩形
  • P3406 海底高铁
  • P2004 领地选择

相关