前缀和与差分(下)

前言

前缀和与差分是算法优化的重要思想,差分可以看做是前缀和的逆运算,上集已经介绍前缀和的相关部分,接下来介绍差分相关部分。

一、差分是什么?

类似数学中的指数与对数,差分可以看成是前缀和的逆运算。

二、差分

作用
差分的作用和前缀和是一样的,都是降低算法的复杂度,简化计算。上一章前缀和求区间和a[l]+…+a[r]的复杂度为o(1),这章是通过差分改变区间值a[l] + … +a[j]也只需o(1)的复杂度。

前缀和:o(1)求区间和
差分:o(1)改变区间值

原数组a[i]: a[1]、a[2]、 … a[i]

差分数组b[i]:b[i] 、b[2] … b[i]

关系: 原数组a[i]是差分数组b[i]的前缀和,即a[i] = b[1] + b[2] +… + b[i]

“差分数组b[i]如何构造”: 反向构造 或者 先初始化为零进行区间[l,r]操作后求差分数组前缀和再与原数组进行相加。

b[1] = a[1] 
b[2] = a[2] - a[1]
b[3] = a[3] - a[2]
...
b[i] =  a[i] - a[i -1]
左边相加 = b[1] + b[2] +.. +b[i]
右边相加 = a[i],即可证该构造合理。

差分数组b[i]有什么用: o(1)改变区间值

问题:
给定区间[l ,r ],让我们把a数组中的[ l, r]区间中的每一个数都加上c,即 a[l] + c , a[l+1] + c , a[l+2] + c , a[r] + c;

暴力做法和前缀和那章一样,m次for循环遍历数组a[n],时间复杂度变成O(n*m)

差分法:
令b[l] = b[l] + c,则根据关系a[l] = b[1] + b[2] +… b[l],则
新数组a[i]中的a[l] = a[l] + c,同理a[l]后面的各项都加上c,即a[l + 1] = a[l+1] + c,a[l + 2] = a[l+1] + c…a[i] = a[i] +c,所以原来数组a[i]就变为:a[1] 、a[2]…a[l]+c、 a[l+1] +c … a[r]+c 、 a[r+1] +c … a[i] +c.
再令b[r + 1] = b[r] -c,则数组a[r] = a[r] - c,同时后面各项都减去c,则最新数组变为:a[1] 、a[1] …a[l]+c 、a[l + 1] +c … a[r] +c、 a[r+1] …a[i] ,即为题目所求,然而该方法时间复杂度为o(n+m)

例题:
输入一个长度为n的整数序列。
接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。
请你输出进行完所有操作后的序列。

输入格式
第一行包含两个整数n和m。第二行包含n个整数,表示整数序列。
接下来m行,每行包含三个整数l,r,c,表示一个操作。

输出格式
共一行,包含n个整数,表示最终序列。

数据范围
1≤n,m≤1000001≤n,m≤100000,
1≤l≤r≤n1≤l≤r≤n,
−1000≤c≤1000−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000−1000≤整数序列中元素的值≤1000

输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2

代码一:

#include <iostream>
using namespace std;

int main(){
    int n,m,l,r,c;//数组长度、询问次数、left边界、right边界,count
    cin>>n>>m;
    int a[n+1] = {0}; //初始化原数组
    int f[n+1] = {0};//构造差分数组
    for(int i = 1;i <= n;i++){
        cin>>a[i];
        f[i] = a[i] - a[i-1];
    }
    
    for(int i = 1;i <= m;i++){
        cin>>l>>r>>c;
        f[l] += c;
        f[r+1] -= c; //差分数组对区间[l,r]操作
    }
    for(int i = 1;i <= n;i++){
        f[i] += f[i-1];//对差分数组前缀和
        std::cout <<f[i] <<" ";
    }
    
    return 0;
}

代码二:

#include <iostream>
using namespace std;

int main(){
    int n,m,l,r,c;//数组长度、询问次数、left边界、right边界,count
    cin>>n>>m;
    int a[n+1]; //从1开始赋值,方便理解
    int f[n+1];//构造差分数组
    int sum[n+1];//差分前缀和
    for(int i = 1;i <= n;i++){
        cin>>a[i];
        f[i] = 0;//初始化差分数组
    }
    
    for(int i = 1;i <= m;i++){
        cin>>l>>r>>c;
        f[l] += c;
        f[r+1] -= c;
        for(int j = 1;j <= n;j++){
            sum[j] = sum[j-1] + f[j];//差分数组前缀和
        }
    }
    for(int i = 1;i <= n;i++){
        std::cout <<a[i] + sum[i] <<" ";//原数组 + 差分数组前缀和
    }
    
    return 0;
}

三、二维差分

作用: o(1)改变子矩阵值(面积)

原数组a[i][j]

差分数组b[i][j]

关系: 原数组a[i][j]是差分数组b[i][j]的二维前缀和

目标:给子矩阵中的每个元素都加上数c,即

已知原数组a中被选中的子矩阵为 以(x1,y1)为左上角,以(x2,y2)为右下角所围成的矩形区域;

for(int i = x1; i <= x2;i++){
	for(int j = y1;j <= y2; j++){
		a[i][j] +=  c;
	}
}

差分方法实现目标:
b[x1][y1] += c;
b[x1][y2+1] -= c;
b[x2+1][y2] -= c;
b[x2+1][y2+1] += c;

差分方法实现目标函数封装

void insert(int x1,int y1,int x2,int y2,int c)
{     //对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
    b[x1][y1]+=c;
    b[x2+1][y1]-=c;
    b[x1][y2+1]-=c;
    b[x2+1][y2+1]+=c;
}

二维差分操作的构造,公式如下:

① b[i][j]=a[i][j]−a[i−1][j]−a[i][j−1]+a[i−1][j−1]

② 使用insert(i,j,i,j,a[i][j])函数进行构造差分数组

如何理解通过insert()构造差分数组?

insert(i,j,i,j,a[i][j])表示对b数组执行插入操作,等价于对a数组中的(i,j)到(i,j)之间的元素(实际是单个小方格,也就是原数组初始化时的赋值操作)加上了a[i][j],
即原数组a[i][j]初始化成功,差分数组通过insert()初始化刚好对应起来.

如何理解差分方法能实现子矩阵中每个元素加上数c呢,请看图
二维差分

同上图,二维差分实现子矩阵各元素加数c解析图image.png
练习题目:
输入一个n行m列的整数矩阵,再输入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 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000
输入样例:
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
代码一: 差分数组全部初始化为零,求差分前缀和后再与原数组相加就是最终答案了

#include<iostream>
using namespace std;
const int N=1e3+10; //1010
int a[N][N],b[N][N] = {0};
void insert(int x1,int y1,int x2,int y2,int c){
    // 表示(x1,y1)---(x2,y2)的小矩阵范围中都加上c
    b[x1][y1]+=c;
    b[x1][y2+1] -= c;
    b[x2+1][y1] -= c;
    b[x2+1][y2+1] += c;
}
int main()
{
    int n,m,q;//分别表示n行m列q个操作
    cin>>n>>m>>q;
    for(int i = 1;i <= n;i++){
        for(int j =1;j <= m;j++){
            cin>>a[i][j];
        }
    }
    
    int x1,y1,x2,y2,c;
    while(q--){
        cin>>x1>>y1>>x2>>y2>>c;
        insert(x1,y1,x2,y2,c);//改变原数组a[i][j]的小矩阵块值 + 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++){
            cout<<a[i][j] + b[i][j]<<" ";//输出
        }
        cout<<endl;
    }
    return 0;
}

代码二: 通过insert函数初始化差分数组

#include<iostream>
using namespace std;
const int N=1e3+10; //1010
int a[N][N],b[N][N];
void insert(int x1,int y1,int x2,int y2,int c){
    // 表示(x1,y1)---(x2,y2)的小矩阵范围中都加上c
    b[x1][y1]+=c;
    b[x1][y2+1] -= c;
    b[x2+1][y1] -= c;
    b[x2+1][y2+1] += c;
}
int main()
{
    int n,m,q;//分别表示n行m列q个操作
    cin>>n>>m>>q;
    for(int i = 1;i <= n;i++){
        for(int j =1;j <= m;j++){
            cin>>a[i][j];
            insert(i,j,i,j,a[i][j]);//通过insert函数巧妙构造差分数组b[i][j]
        }
    }
    
    int x1,y1,x2,y2,c;
    while(q--){
        cin>>x1>>y1>>x2>>y2>>c;
        insert(x1,y1,x2,y2,c);//改变原数组a[i][j]的小矩阵块值 + 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++){
            cout<<b[i][j]<<" ";//输出
        }
        cout<<endl;
    }
    return 0;
}

四、三维差分

一旦到三维甚至更高维,其几何空间很难想象。下面给出三维差分实现子体积中每个小体积加上数h的相关公式。

三维差分8个式子

二进制表示(方便记忆)

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

000	B(X1, Y1, Z1) += C   ——使该立方体全部加上常数C
001	B(X1, Y1, Z2 + 1) -=C
010	B(X1, Y2 + 1, Z1) -= C
011	B(X1, Y2 +1, Z2 + 1) += C
100	B(X2 +1, Y1, Z1) -= C
101	B(X2 + 1, Y1, Z2 + 1) += C
110	B(X2 + 1, Y2 + 1, Z1) += C
111	B(X2 + 1, Y2 + 1, Z2 + 1) -= C

三维差分方法封装

void insert(int x1,int y1,int z1,int x2,int y2,int z2,int h)
{//对b数组执行插入操作,等价于对a数组中的(x1,y1,z1)到(x2,y2,z2)之间的元素都加上了h
    b(x1, y1, z1) +=h
	b(x1, y1, z2+1) -=h
	b(x1, y2+1, z1) -=h
	b(x1, y2+1, z2+1) +=h
	b(x2+1, y1, z1) -=h
	b(x2+1, y1, z2+1) +=h
	b(x2+1, y2+1, z1) +=h
	b(x2+1, y2+1, z2+1) -=h
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值