差分,是一种和前缀和相对的策略。
差分概念
对于一个数列 ,我们需要维护的数据是“相邻两个数之差”。这种策略是,令
,即相邻两数的差。我们称数列
为数列
的差分数列。
应用
它可以维护多次对序列的一个区间加上一个数,并在最后询问某一位的数或是多次询问某一位的数。譬如使 [l, r] 每个数加上一个 k,就是 ,最后做一遍前缀和。
就是对这个差分数列 做一遍前缀和就得到了原来的数列
,即
相当于
这个前缀和。
证明如下:
这样,我们可以发现一个规律,即第二个多项式的系数为 i-1。我们用 来维护这个数组,那么
,并且在修改时候维护
数组,即
只后便有了公式
。
举例
目前 ,则对应的差分数列
。
给 [3, 5] 加上首项为2、公差为 1 的等差数列,及 。那么原数列变为
,对差分数列变为
。
说明 a 区间加等差,相当于 p 区间加常数、端点单点修改。也就是区间加自然是给 加上 d。
模板题
题目链接
我的OJ,http://47.110.135.197/problem.php?id=5226。
题目描述
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l, r, c,表示将序列中 [l, r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
输入
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。
输出
共一行,包含 n 个整数,表示最终序列。
样例输入
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
样例输出
3 4 5 3 4 2
分析
这是一个一维差分的模板题。
数据分析
下面我们根据样例输入来分析一下,样例输出是如何得到的。
初始状态的差分数组 diff 为 1 1 0 -1 1 -1。注意第一个下标为 1。
第一次操作为 1 3 1,那么就是 diff[1] = diff[1]+1,diff[4] = diff[4]-1,得到差分数组 diff 变为 2 1 0 -2 1 -1。
第二次操作为 3 5 1,那么就是 diff[3] = diff[3]+1,diff[6] = diff[6]-1,得到差分数组 diff 变为 2 1 1 -2 1 -2。
第三次操作为 1 6 1,那么就是 diff[1] = diff[1]+1,diff[7] = diff[7]-1,得到差分数组 diff 变为 3 1 1 -2 1 -2 -1。
因此,最终的原始数组如下:
a[1] = diff[0]+diff[1] = 3
a[2] = diff[0]+diff[1]+diff[2] = 4
a[3] = diff[0]+diff[1]+diff[2]+diff[3] = 5
a[4] = diff[0]+diff[1]+diff[2]+diff[3]+diff[4] = 3
a[5] = diff[0]+diff[1]+diff[2]+diff[3]+diff[4]+diff[5] = 4
a[6] = diff[0]+diff[1]+diff[2]+diff[3]+diff[4]+diff[5]+diff[6] = 2
数据范围
从题目中知道,n 的最大值为 100000,因此我们定义数组为 100004。
数组的每个数范围为 [-1000, 1000],c 的范围为 [-1000, 1000],操作数 m 最大值为 100000。因此我们可以计算出,经过 m 次操作后,最大的数据为 1000+1000*100000 = 10^8+1000,在 int 的表示范围内。同理最小的数据将是 -1000+(-1000*100000)=-10^8-1000,也在 int 的表示范围内。
AC代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+6;
int data[MAXN] = {};
int diff[MAXN] = {};
int main() {
int n,m;
scanf("%d%d", &n, &m);
int i;
for (i=1; i<=n; i++) {
scanf("%d", &data[i]);
diff[i] = data[i] - data[i-1];
}
for (i=0; i<m; i++) {
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
diff[l] += c;
diff[r+1] -= c;
}
//输出
int ans=diff[0];
for (i=1; i<=n; i++) {
ans += diff[i];
printf("%d ", ans);
}
printf("\n");
return 0;
}