AcWing 算法基础课笔记【基础算法篇】4.前缀和与差分

AcWing 算法基础课笔记【基础算法篇】4.前缀和与差分

一维前缀和

思想

假设有一个数组 A : A 1 , A 2 , . . . , A n A:A_{1},A_{2},...,A_{n} A:A1,A2,...,An
那么可以定义一个前缀和数组 S : S i = A 1 + A 2 + . . . + A i S:S_{i}=A_{1}+A{2}+...+A_{i} S:Si=A1+A2+...+Ai
其中, S 0 = 0 S_{0}=0 S0=0

作用

求原数组中一段数的和
若不使用前缀和数组,则必须一位一位的加,时间复杂度为 O ( n ) O(n) O(n)
但是使用前缀和数组,预处理的时间复杂度为 O ( n ) O(n) O(n),之后每次计算的时间复杂度为 O ( 1 ) O(1) O(1),即 A l + . . . + A r = S r − S l − 1 A_{l}+...+A_{r}=S_{r}-S_{l-1} Al+...+Ar=SrSl1
递归求 S i S_{i} Si
S i = S i − 1 + a i S_{i}=S_{i-1}+a_{i} Si=Si1+ai

代码

const int N = 100010;
int a[N], s[N];

int main(){
    int n, m;
    scanf("%d %d", &n, &m);
    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]);
    }
    
    return 0;
}

二维前缀和

思想

S i , j S_{i,j} Si,j表示矩阵中以 ( 0 , 0 ) 、 ( i , j ) (0,0)、(i,j) (0,0)(i,j)为边界点的子矩阵中所有元素的和
假设要求以 ( x 1 , y 1 ) 、 ( x 2 , y 2 ) (x_{1},y_{1})、(x_{2},y_{2}) (x1,y1)(x2,y2)分别为左上角、右下角坐标的子矩阵中的元素和
则等于 S x 2 , y 2 − S x 2 , y 1 − 1 − S x 1 − 1 , y 2 + S x 1 − 1 , y 1 − 1 S_{x_{2},y_{2}}-S_{x_{2},y_{1}-1}-S_{x_{1}-1,y_{2}}+S_{x_{1}-1,y_{1}-1} Sx2,y2Sx2,y11Sx11,y2+Sx11,y11
如何递推求 S i , j S_{i,j} Si,j
S i , j = S i − 1 , j + S i , j − 1 − S i − 1 , j − 1 + a i , j S_{i,j}=S_{i-1,j}+S_{i,j-1}-S_{i-1,j-1}+a_{i,j} Si,j=Si1,j+Si,j1Si1,j1+ai,j

代码

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

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]);
            
    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, y1, x2, 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]);
    }
    
    return 0;
}

一维差分

思想

假设有一个数组a,构造一个数组b,使得数组a是数组b的前缀和数组
此时,b为a的差分数组

作用

如果要多次将原数组a的 [ l , r ] [l,r] [l,r]区间内的所有元素加上 c c c,时间复杂度为 O ( 次数 ∗ n ) O(次数*n) O(次数n)
此时,可以将 b l + c 、 b r + 1 − c b_{l}+c、b_{r+1}-c bl+cbr+1c,最后再根据b数组推出原数组a,时间复杂度为 O ( n + 次数 ) O(n+次数) O(n+次数)
如何构造数组b:
将差分数组初始都设为0,可以看成在 [ i , i ] [i,i] [i,i]范围内加上 a i a_{i} ai

代码

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

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++) insert(i, i, a[i]); // 构造差分数组b
    
    while(m--){ // 处理数组
        int l, r, c;
        scanf("%d %d %d", &l, &r, &c);
        insert(l, r, c);
    }
    
    for(int i = 1; i <= n; i++) a[i] = a[i - 1] + b[i]; // 递归求数组a
    for(int i = 1; i <= n; i++) printf("%d ", a[i]);
    
    return 0;
}

二维差分

作用

b i , j + = c b_{i,j}+=c bi,j+=c表示在原矩阵a中,以点 ( i , j ) (i,j) (i,j)作为左上角,以整个矩阵的右下角作为右下角,这个子矩阵中的所有元素加上c
假设要操作的矩阵,左上角坐标为 ( x 1 , y 1 ) (x_{1},y_{1}) (x1,y1),右下角坐标为 ( x 2 , y 2 ) (x_{2},y_{2}) (x2,y2),将这个范围内的子矩阵加上c:
b x 1 , y 1 + = c , b x 2 + 1 , y 1 − = c , b x 1 , y 2 + 1 − = c , b x 2 + 1 , y 2 + 1 + = c b_{x_{1},y_{1}}+=c,b_{x_{2}+1,y_{1}}-=c,b_{x_{1},y_{2}+1}-=c,b_{x_{2}+1,y_{2}+1}+=c bx1,y1+=c,bx2+1,y1=c,bx1,y2+1=c,bx2+1,y2+1+=c
递归求 a i , j a_{i,j} ai,j
a i , j = a i − 1 , j + a i , j − 1 − a i − 1 , j − 1 + b i , j a_{i,j}=a_{i-1,j}+a_{i,j-1}-a_{i-1,j-1}+b_{i,j} ai,j=ai1,j+ai,j1ai1,j1+bi,j

代码

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

void insert(int x1, int y1, int x2, int y2, int c){
    b[x1][y1] += c;
    b[x2 + 1][y1] -= c;
    b[x1][y2 + 1] -= 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]);
            
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            insert(i, j, i, j, a[i][j]);
            
    while(q--){
        int x1, y1, x2, 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++)
            a[i][j] = a[i][j-1] + a[i-1][j] - a[i-1][j-1] + b[i][j];
            
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++)
            printf("%d ", a[i][j]);
        puts("");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值