基础算法 前缀和与差分

基础算法 前缀和与差分

学习地址:AcWing

一、前缀和


一维前缀和

原数组:\(a_1、a_2、a_3、a_4、a_5.....a_n\)

前缀和:\(S_i=a_1+a_2+a_3+...+a_i\)\(S_0=0\) 从1开始,为了处理边界问题

求区间和:\(S_{l-r}=S_r-S_{l-1}\)

\(S_i\):可以同样用区间和求前缀和:\(S_i=S_i-S_0\),如\(S_{10}=S_{10}-S_0\)

例题:795. 前缀和 - AcWing题库

for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i]; // 前缀和的初始化

while (m--) {
    int l, r;
    scanf("%d%d", &l, &r);
    printf("%d\n", s[r] - s[l - 1]);	// 区间和的计算
}

二维前缀和

容斥原理的思想

二维原数组 \(a_{11},a_{12},,,,a_{1m}\)

\(a_{21},a_{22},,,,a_{2m}\)

​ ,,,

\(a_{n1},a_{n2},,,,a_{nm}\)

前缀和:\(S_{ij}=S_{i-1j}+S_{ij-1}-S_{i-1j-1}+a_{ij}\)

区间和:\(S[x_1\sim x_2,y_1\sim y_2]=\)\(S_{x_2y_2}-S_{x_2y_1-1}-S_{x_1-1y2}+S_{x_1-1y_1-1}\)

image

例题:796. 子矩阵的和 - AcWing题库

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--) {
    int x1, x2, y1, y2;
    scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
    printf("%d\n", s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]); // 区间和、部分和、子矩阵的和
}

二、差分


差分为前缀和的逆运算

假设函数\(S=f(x)\) ,为求x 的前缀和,则\(a = f(b)\)\(b = f^{-1}(a)\)

一维差分

区间修改\(O(n)\)

数组a\(a_1,a_2,a_3,,,,,,a_n\) (前缀和)

构造数组b\(b_1,b_2,b_3,,,,,,b_n\) (差分)

使得:ab的前缀和:\(a_i=b_1+b_2+b_3+,,,+b_i\)b称为a的差分,对b求一下前缀和得到a

满足:\(b_1=a_1\)\(b_2=a_2-a_1\)\(b_3=a_3-a_2\),,,,\(b_n=a_n-a_{n-1}\)

a的一些操作,比如在a[l~r]区间全部数加上一个c,只需要在差分b里的b[l]加上cb[r+1]

减去c,最后再求解前缀和就可以。

例题:797. 差分 - AcWing题库

#include <iostream>
using namespace std;

const int N = 1000010;
int n, m;
int a[N], b[N];

// 求差分数组、插入部分可用此函数代替,insert(i, i, a[i]) insert(l, r, c)
void insert(int l, int r, int c) {
    b[l] += c;
    b[r + 1] += c;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) b[i] = a[i] - a[i - 1]; // 求差分数组
    while (m--) {
        int l, r, c;
        scanf("%d%d%d", &l, &r, &c);
        b[l] += c, b[r + 1] -= c; 	// 插入
    }
    for (int i = 1; i <= n; i++) {
        b[i] += b[i - 1]; // 求前缀和
    }
    for (int i = 1; i <= n; i++) {
        printf("%d ", b[i]);
    }
    return 0;
}

二维差分

容斥原理的思想

二维原数组a \(a_{11},a_{12},,,,a_{1m}\) 构造差分数组b \(b_{11},b_{12},,,,b_{1m}\)

\(a_{21},a_{22},,,,a_{2m}\) \(b_{21},b_{22},,,,b_{2m}\)

​ ,,, ,,,

\(a_{n1},a_{n2},,,,a_{nm}\) \(b_{n1},b_{n2},,,,b_{nm}\)

使得ab的前缀和数组,ba的差分数组

a[x1,y1~x2,y2]全部数加上c,等价于:

  1. \(b_{x_1,y_1} += c\) 整个右下角全部加上c

  2. \(b_{x_1,y_2+1} -= c\) 下面的部分

  3. \(b_{x_2+1,y_1} -= c\) 右面的部分

  4. \(b_{x_2+1,y_2+1} += c\) 加上重复减去的右下角

而初始的数组,a[i,j]相当于对a[i,j~i,j]加上a[i,j]

image

例题:798. 差分矩阵 - AcWing题库

#include <iostream>
using namespace std;

const int N = 1010;
int n, m, q;
int a[N][N], b[N][N];

void insert(int x1, int y1, int x2, int y2, int c) {
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += 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]);
            insert(i, j, i, j, a[i][j]); // 初始化差分数组
        }
    }
    
    while (q--) {
        int x1, x2, y1, y2, c;
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        insert(x1, y1, x2, y2, c);		// 继续构造差分
    }
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];	// 计算前缀和
        }
    }
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            printf("%d ", b[i][j]);	// 输出
        }
        printf("\n");
    }
    return 0;
}
posted @ 2022-01-13 17:14  关键概念  阅读(80)  评论(0编辑  收藏  举报