前缀和与差分

01章 前缀和 预处理思想是在进行正式求解之前先将一些不变的信息处理出来以达到优 化复杂度的目的。 预处理思想最经典的就是前缀和。 前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和, 而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复 杂的问题简单化。 前缀和算法有什么好处? 先来了解这样一个问题: 一维前缀和: 输入一个长度为n的整数序列。接下来再输入m个询问,每个询问输入一 对l, r。对于每个询问,输出原序列中从第l个数到第r个数的和。 我们很容易想出暴力解法,遍历区间求和。 int n,m; scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); while(m--){ int l, r; int sum = 0; scanf("%d%d", &l, &r); for(int i = l; i <= r; i++) { sum += a[i]; } printf("%d\n",sum); } 这样的时间复杂度为O(n * m),如果n和m的数据量稍微大一点就有可能超时,而我 们如果使用前缀和的方法来做的话就能够将时间复杂度降到O(n + m),大大提高了运 算效率。 具体做法: 首先做一个预处理,定义一个sum[]数组,sum[i]代表a数组中前i个数的和。 for(int i = 1;i <= n; i++){ sum[i] = sum[i - 1] + a[i]; } 然后查询操作: scanf("%d%d",&l,&r); printf("%d\n", sum[r] - sum[l - 1]); 对于每次查询,只需执行sum[r] - sum[l - 1] ,时间复杂度为O(1) 原理 sum[r] = a[1] + a[2] + a[3] + a[l-1] + a[l] + a[l + 1] ...... a[r]; sum[l - 1] = a[1] + a[2] + a[3] + a[l - 1]; sum[r] - sum[l - 1] = a[l] + a[l + 1] + ......+ a[r]; 这样,对于每个询问,只需要执行 sum[r] - sum[l - 1]。输出原序列中从第l个数到第 r个数的和的时间复杂度变成了O(1)。 我们把它叫做一维前缀和。 例1:3857: 前缀和 题目描述 给一个序列a1, a2,…, an和m个询问。 每次询问给出两个数l,r,求 al+al+1+⋯+ar的值。 02章 差分 一维差分 差分可以看成前缀和的逆运算。 首先给定一个原数组a:a[1], a[2], a[3],,,,,, a[n]; 然后我们构造一个数组b : b[1], b[2], b[3],,,,,, b[i]; 使得 a[i] = b[1] + b[2] + b[3] + ,,,,,, + b[i] 也就是说,a数组是b数组的前缀和数组,反过来我们把b数组叫做a数组 的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。 考虑如何构造差分b数组? 最为直接的方法: 我们只要有b数组,通过前缀和运算,就可以在O(n) 的时间内得到 a 数 组 。 知道了差分数组有什么用呢? 别着急,慢慢往下看。 给定区间[l, r ],让我们把a数组中的[l, r] 区间中的每一个数都加上c,即 a[l] + c , a[l + 1] + c , a[l + 2] + c ,,,,,, a[r] + c; 暴力做法是for循环l到r区间,时间复杂度O(n),如果我们需要对原数组执 行m次这样的操作,时间复杂度就会变成O(n * m)。有没有更高效的做法吗? 考虑差分做法,(差分数组派上用场了)。 始终要记得,a数组是b数组的前缀和数组,比如对b数组的b[i]的修改, 会影响到a数组中从a[i]及往后的每一个数。 首先让差分b数组中的 b[l] + c ,通过前缀和运算,a数组变成 a[l] + c ,a[l + 1] + c,,,,,, a[n] + c; 然后我们打个补丁,b[r + 1] - c, 通过前缀和运算,a数组变成 a[r + 1] - c,a[r + 2] - c,,,,,,,a[n] - c; 我们画个图理解一下这个公式的由来: b[l] + c,效果使得a数组中 a[l] 及以后的数都加上了c(红色部分),但我 们只要求l到r 区间加上 c, 因此还需要执行 b[r + 1] - c,让a数组中 a[r + 1]及 往后的区间再减去c(绿色部分),这样对于a[r] 以后区间的数相当于没有发生 改变。 因此我们得出一维差分结论:给a数组中的[ l, r] 区间中的每一个数都加上 c,只需对差分数组b做 b[l] + = c, b[r+1] - = c 。时间复杂度为O(1), 大大提 高了效率。 例1:0001 123456 题目描述 123456 03章 二维差分 前缀和 二维前缀和: 二维前缀和如果数组变成了二维数组怎么办呢? 先给出问题: 输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出 子矩阵中所有数的和。 同一维前缀和一样,我们先来定义一个二维数组s[][] , s[i][j] 表示二维数组中 ,左上角(1, 1)到右下角(i, j)所包围的矩阵元素的和。接下来推导二维前缀和 的公式。 先看一张图: 紫色面积是指(1, 1)左上角到(i, j - 1)右下角的矩形面积, 绿色面积是指(1, 1) 左上角到(i - 1, 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 ] + a[i] [j] - s[i - 1][j - 1] 因此二维前缀和的结论为: 以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为: s[ x2 ][ y2 ] - s[ x1 - 1 ][ y2 ] - s[ x2 ][ y1 - 1 ] + s[ x1 - 1 ][ y1 - 1 ]; 二维差分 如果扩展到二维,我们需要让二维数组被选中的子矩阵中的每个元素的值加 上c,是否也可以达到O(1)的时间复杂度。答案是可以的,考虑二维差分。 a[][]数组是b[][]数组的前缀和数组,那么b[][]是a[][]的差分数组 原数组: a[i][j] 我们去构造差分数组: b[i][j] 使得a数组中a[i][j]是b数组左上角(1,1)到右下角(i,j)所包围矩形元素的和。 如何构造b数组呢? 其实关于差分数组,我们并不用考虑其构造方法,因为我们使用差分操作在 对原数组进行修改的过程中,实际上就可以构造出差分数组。 同一维差分,我们构造二维差分数组目的是为了 让原二维数组a中所选中子矩 阵中的每一个元素加上c的操作,可以由O(n*n)的时间复杂度优化成O(1) 已知原数组a中被选中的子矩阵为 以(x1,y1)为左上角,以(x2,y2)为右下角所 围成的矩形区域; 始终要记得,a数组是b数组的前缀和数组,比如对b数组的b[i][j]的修改,会 影响到a数组中从a[i][j]及往后的每一个数。 假定我们已经构造好了b数组,类比一维差分,我们执行以下操作 来使被选中的子矩阵中的每个元素的值加上c b[x1][y1] + = c ; b[x1,][y2+1] - = c; b[x2+1][y1] - = c; b[x2+1][y2+1] + = c; 每次对b数组执行以上操作,等价于: for(int i = x1;i <= x2;i++) for(int j = y1;j <= y2;j++) a[i][j] += c; 我们画个图去理解一下这个过程: b[x1][y1] += c ; 对应图1 ,让整个a数组中蓝色矩形面积 的元素都加上了c。 b[x1,][y2 + 1] -= c ; 对应图2 ,让整个a数组中绿色矩形 面积的元素再减去c,使其内元素不发生改变。 b[x2 + 1][y1] -= c ; 对应图3 ,让整个a数组中紫色矩形 面积的元素再减去c,使其内元素不发生改变。 b[x2 + 1][y2 + 1] += c; 对应图4,让整个a数组中红色矩 形面积的元素再加上c,红色内的相当于被减了两次,再 加上一次c,才能使其恢复。 我们将上述操作封装成一个插入函数: 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; } 我们可以先假想a数组为空,那么b数组一开始也为空,但是实际上a数组并不 为空,因此我们每次让以(i,j)为左上角到以(i,j)为右下角面积内元素(其实就是 一个小方格的面积)去插入 c = a[i][j] ,等价于原数组a中(i,j) 到(i,j)范围内 加上了 a[i][j] ,因此执行 n*m次插入操作,就成功构建了差分b数组. for(int i = 1;i <= n;i++){ for(int j = 1;j <= m;j++){ insert(i, j, i, j, a[i][j]); //构建差分数组 } } 例1:0001 123456 题目描述 123456 04章 前缀和 拓展 二维前缀和: 二维前缀和如果数组变成了二维数组怎么办呢? 先给出问题: 输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出 子矩阵中所有数的和。 同一维前缀和一样,我们先来定义一个二维数组s[][] , s[i][j] 表示二维数组中 ,左上角(1, 1)到右下角(i, j)所包围的矩阵元素的和。接下来推导二维前缀和 的公式。 二维前缀和: 二维前缀和如果数组变成了二维数组怎么办呢? 先给出问题: 输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出 子矩阵中所有数的和。 同一维前缀和一样,我们先来定义一个二维数组s[][] , s[i][j] 表示二维数组中 ,左上角(1, 1)到右下角(i, j)所包围的矩阵元素的和。接下来推导二维前缀和 的公式。 05章 差分拓展 二维前缀和: 二维前缀和如果数组变成了二维数组怎么办呢? 先给出问题: 输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出 子矩阵中所有数的和。 同一维前缀和一样,我们先来定义一个二维数组s[][] , s[i][j] 表示二维数组中 ,左上角(1, 1)到右下角(i, j)所包围的矩阵元素的和。接下来推导二维前缀和 的公式。 二维前缀和: 二维前缀和如果数组变成了二维数组怎么办呢? 先给出问题: 输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出 子矩阵中所有数的和。 同一维前缀和一样,我们先来定义一个二维数组s[][] , s[i][j] 表示二维数组中 ,左上角(1, 1)到右下角(i, j)所包围的矩阵元素的和。接下来推导二维前缀和 的公式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值