差分与前缀和
1. 差分法 (解决区间加减问题)
-
当某一个数组要在很多不确定的区间,加上相同的一个数。我们如果每个都进行加法操作的话,那么复杂度 O(nm) 是平方阶的,非常消耗时间。
-
如果我们采用差分法,将数组拆分,构造出一个新的拆分数组,通过对数组区间的端点进行加减操作,最后将数组和并就能完成原来的操作。时间复杂度降低为 O(N)
-
差分法的特点:
-
将对于区间的加减操作转化为对于端点的操作;
-
时间复杂度为 O(n);
-
用于维护区间的增减但不能维护乘除;
-
差分后的序列比原来的数组序列多一个数。
一个序列1 2 5 4 7 3,差分后得到1 1 3 -1 3 -4 -3
这里注意得到的差分序列第一个数和原来的第一个数一样(相当于第一个数减0)
差分序列最后比原序列多一个数(相当于0减最后一个数)
(1) 差分算法解题的基本思路:
若原数组是a[ ] ,进行差分后得到的数组是b[ ]
- b[1]=a[1];
- 从第 2 项到 n 项,利用 b[i]=a[i]-a[i-1] 差分式;
- 对于区间端点操作加减;对于每个 [l,r] 区间的加减操作都转化为对端点 l,r+1 的操作
- 首先假设有一个数组:
a[]={1 2 3 4 5 7 2}
差分后:(利用 b[i]= a[i]-a[i-1] 差分式)
b[]={1 1 1 1 1 2 -5 -2}
一般应用场景:
让你对区间 [l,r] 加减操作 N 次
如:
从第二个元素到第五个元素每个+3
从第二个元素到第四个元素每个-2
从第一个元素到第三个元素每个+1
对于每个 [l,r] 区间的加减操作都转化为对端点 l,r+1 的操作
从第二个元素到第五个元素每个+3:
转化为:[l]+3 并且 [r+1]-3那么原序列变成了:
1 1 1 1 1 2 -5 -2
1 4 1 1 1 -1 -5 -2
然后我们按照 b[i]=b[i]+b[i-1] 复原:
1 5 6 7 8 7 2 0
去掉最后一项,跟原序列对比:
1 2 3 4 5 7 2
1 5 6 7 8 7 2
确实是都加上了 3。
我们继续操作:
从第二个元素到第四个元素每个-2
转化为:[l]-2 并且 [r+1]+2
那么序列变成了:
1 4 1 1 1 -1 -5
1 2 1 1 3 -1 -5
然后我们按照b[i]=b[i]+b[i-1] 复原
1 3 4 5 8 7 2
与上次复原后对比:
1 5 6 7 8 7 2
1 3 4 5 8 7 2
确实是按照操作执行了。
不用每次都复原,只用最后一次复原即可,这里演示给大家看。
直接做三次,最后还原:
从第二个元素到第五个元素每个+3从第二个元素到第四个元素每个-2
从第一个元素到第三个元素每个+1
a[]={1 2 3 4 5 7 2}
原序列差分后:b[]={1 1 1 1 1 2 -5}
2 号元素 + 3
6 号元素 - 3
2 号元素 - 2
5 号元素 + 2
1 号元素 + 1
4 号元素 - 1
差分序列变成:
2 2 1 0 3 -1 -5
复原后:2 4 5 5 8 7 5
与原序列对比:
1 2 3 4 5 7 2
2 4 5 5 8 7 5
所以还是非常方便快捷的。 -
(2)例题:
题目描述:
教室外有 N 棵树,根据不同的位置和树种,学校要对其上不同的药。 因为树的排列成线性,且非常长,我们可以将它们看作一条直线给他们编号。 树的编号从 0--N-1 且 N<1e6。 对于树的药是成区间分布,比如 3 - 5 号的树靠近下水道,所以他们要用驱蚊虫的药, 20 - 26 号的树,他们排水不好,容易涝所以要给他们用点促进根系的药。 诸如此类,每种不同的药要花不同的钱。 现在已知共有 M 个这样的区间,并且给你每个区间花的钱,请问最后,这些树木花了多少药费。
输入:
输入描述: 每组输入的第一行有两个整数 N(1 <= N<= 1000000)和 M(1 <= M <= 100000)。 N 代表马路的共计多少棵树,M代表区间的数目,N 和 M 之间用一个空格隔开。 接下来的 M 行每行包含三个不同的整数,用一个空格隔开,表示一个区域的起始点 L 和终止点 R 的坐标,以及花费。
输入样例:
500 3
150 300 4
100 200 20
470 471 19
输出描述: 输出包括一行,这一行只包含一个整数,所有的花费。
输出样例:
2662
题目解析:
-
利用b[i]=a[i]-a[i-1] 差分式。
这里由于开始时都是 0,可以用,但是用完都还是 0,所以没有意义,所以直接跳过即可。
-
依次读入区间的值,然后将对于区间的操作转化为对于区间端点操作加减。 由于我们从1开始,所以数目整体区间要右移1位。
对于每个 [l,r] 区间的加减操作都转化为对端点 l,r+1 的操作。
-
差分还原(前缀和)。
差分算法解决区间加减问题通用框架
//读入原始数据 n,m,a 输入n,m for(int i=1;i<=n;i++){ 输入a[i] } //差分 for(int i=1;i<=n;i++) b[i]=a[i]-a[i-1] //区间操作 while(m--) { 输入l,r,value b[l]+value b[r+1]-value } //前缀和还原 for(int i=1;i<n;i++){ b[i]=b[i]+b[i-1] }
代码实现:
package 差分与前缀和; import java.util.Scanner; public class ChaFen { static int b[]=new int [100005];//存放每个区间花费的数组 public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n;// 代表马路的共计多少棵树 int m;// 代表区间的数目 n = scanner.nextInt(); m = scanner.nextInt(); // 接下来的 M 行每行包含三个不同的整数,用一个空格隔开,表示一个区域的起始点 L 和终止点 R 的坐标,以及花费 while (m > 0) { m--; int l, r, value; l = scanner.nextInt(); r = scanner.nextInt(); value = scanner.nextInt(); b[l+1]+=value;//注意每个区间的起始点 L 和终止点 R 的坐标 b[r+1+1]-=value; } //差分还原 int sum=0;//总钱数 for(int i=1; i<=n; i++) { b[i]=b[i]+b[i-1]; sum+=b[i]; } System.out.println(sum); } }
2. 前缀和 (解决区间求和问题)
- 前缀和是指某序列的前 n 项和,可以把它理解为数学上的数列的前 n 项和。当对于某一数组区间进行多次询问[L,r] 的和时,如果正常处理,那么我们每次都要 [l,r]。查询 N 次,那么时间复杂度也是 O(nm) 也是平方阶的。
- 如果我们采用前缀和,构造出一个前缀和数组,通过对于端点的值的减法操作就能 O(1) 的求出 [l,r] 的和。然后 N 次查询的,就将复杂度降低为 O(n)
前缀和的特点:
- 将对于区间的求和操作转化为对于端点值的减法的操作;
- 区间求和操作的时间复杂度为 O(1);
- 数组存放时要从 1 开始;
- 前缀和数组比原来的数组序列多一个数,第 0 个
前缀和算法解题的基本思路:
- 利用 sum[i]=a[i]+sum[i-1] 差分式;
- 从第 1 项到 n 项,且第 0 项无数据默认为 0;
- 对于区间求和的操作转化为端点值相减。
前缀和的一般解题过程:
/*首先假设有一个数组:
1 2 3 4 5 7 2
前缀和后:
0 1 3 6 10 15 22 24
一般应用场景:
让你对区间 [l,r] 求和操作N次
如:
从第二个元素到第五个元素的和
从第二个元素到第四个元素的和
....
这里我们先演示前2个:
对于每个 [l,r] 区间的求和操作转化为区间端点的加减操作
sum[l,r] = sum[r]- sum[l-1]
从第二个元素到第五个元素的和:
转化为: sum[5]- sum[1]
那么Sum[2,5]= sum[5]- sum[1]=15-1=14
在原序列中:2+3+4+5=14
确实是相等的,就是这么神奇。
我们继续操作:
从第二个元素到第四个元素的和
转化为:[4]-[1]
那么Sum[2,4]=[4]-[1]=9
且 2+3+4=9
*/
例题:
题目描述:
教室外有 N 棵树,根据不同的位置和树种,学校已经对其进行了多年的维护。因为树的排列成线性,且非常长,我们可以将它们看作一条直线给他们编号。 树的编号从 1--N 且 N<1e6。由于已经维护了多年,每一个树都由学校的园艺人员进行了维护费用的统计。 每棵树的前期维护费用各不相同,但是由于未来需要要打药,所以有些树木的维护费用太高的话,就要重新种植。由于维护费用也称区间分布,所以常常需要统一个区间里的树木的维护开销。 现在园艺人员想知道,某个区间内的树木维护开销是多少。共计 M 个区间需要查询。
输入描述:
每组输入的第一行有两个整数 N(1 <= N<= 1000000)和 M(1 <= M <= 100000)。 N 代表马路的共计多少棵树,M 代表区间的数目,N 和 M 之间用一个空格隔开。接下来的一行,包含 N 个数,每个数之间用空格隔开。 接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点L和终止点R的坐标。 输入样例:
10 3
7 5 6 4 2 5 0 8 5 3
1 5
2 6
3 7
输出描述:
输出包括M行,每一行只包含一个整数,所有的花费。
输出样例:
24
22
17
题目解析:
- 利用sum[i]=a[i]+sum[i-1] 前缀和式在输入时求出前缀和;
- 依次读入区间的值,然后将对于区间的求和操作转化为对于区间端点操作加减,对于每个 [l,r] 区间的求和操作都转化为对端点[r]-[l-1]的操作。
- 输出答案。
前缀和一般解题过程:
//输 入 N 和 M
//输入 N 个值 并计算前缀和
for( int i=1;i<=N;i++)
输入a[i]
并计算sum[i]=sum[i-1]+a[i]
//输入 M 个区间,计算结果
while(M)
M--
输入 L , R
计算 [r]-[l-1],并输出
代码实现:
package 差分与前缀和;
import java.util.Scanner;
//所以此题是按树的编号作为了每一个树的维护费用
public class QianZhui {
static int a[]=new int [100005];
static int sum[]=new int [100005];
public static void main(String[] args) {
Scanner sanner = new Scanner(System.in);
int n; //n棵树
int m; // m个区间
n = sanner.nextInt();
m = sanner.nextInt();
for(int i=1;i<=n;i++)
{
a[i]= sanner.nextInt();
sum[i]=a[i]+sum[i-1];
}
while(m>0)
{
m--;
int l,r;
l = sanner.nextInt();
r = sanner.nextInt();
System.out.println((sum[r]-sum[l-1]));
}
}
//验证:
//a[i] = 7 5 6 4 2 5 0 8 5 3
//前缀和:
//sum[i]= 0 7 12 18 22 24 29 29 37 42 45
//比如:1和5的起终点
sum[5]-sum[0] = 24-0=24
7+5+6+4+2=24
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现