AcWing 797 差分算法详解

输入一个长度为 n 的整数序列。

接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。

请你输出进行完所有操作后的序列。

输入格式
第一行包含两个整数 n 和 m。

第二行包含 n 个整数,表示整数序列。

接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。

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

数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤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

类似于数学中的求导和积分,差分可以看成前缀和的逆运算
差分数组
首先给定一个原数组a: a[1],a[2],a[3],a[n]
然后我们构造一个数组b:b[1],b[2],b[n]
使得a[i] = b[1] + b[2] + b[3] + b[4] + , + b[i]
也就是说,a数组是b数组的前缀和数组,反过来我们我们把b数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。
考虑如何构造差分b数组?
最为直接的方法
如下:
a[0] = 0
b[1] = a[1] - a[0];
b[2] = a[2] - a[1];
b[3] = a[3] - a[2];
b[4] = a[4] - a[3];

b[n] = a[n] - a[n - 1];

这样,我们只要有了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(m*n)。有没有更高效的做法呢?考虑差分做法。
始终要记得,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),大大提高了效率。

cpp代码

//差分 时间复杂度O(n)
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for(int i = 0; i <= n; i++)
	{
		scanf("%d", &a[i]);
		b[i] = a[i] - a[i - 1];//构建差分数组
	}
	int l, r, c;
	while(m--)
	{
		scanf("%d%d%d", &l, &r, &c);
		b[l] += c; //将序列中[l, r]之间每个数都加上c
		b[r + 1] -= c;
	}
	for(int i = 1; i <= n; i++)
	{
		a[i] = b[i] + a[i - 1];//前缀和运算
		printf("%d ", a[i]);
	}
	return 0;
}
```
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值