目录
差分法
差分法的应用主要是用于处理区间问题,当一个数组要在很多不确定的区间,加上相同的一个数,我们如果每个数都进行加法操作的话,那么复杂度是平方阶的,非常消耗时间。
如果我们使用差分法,将数组拆分,构造出一个新的拆分数组,通过对数组区间的端点进行加减操作最后将数组合并就能完成原来的操作。这样处理后,时间复杂度降为。
差分法的特点:
1.将对于区间的加减操作转化为对于端点的操作。
2.时间复杂度为。
3.用于维护区间的增减但不能维护乘除。
4.差分后的序列比原来的数组序列多一个数。
//读入原始数据
输入n,m
原始数组 a[]
差分数组 b[]
for i(1-n)
输入a[i]
//差分
for i(1-n)
b[i]=a[i]-a[i-1]
//m次区间操作
while(m--)
输入l,r,value
区间加法改为:
b[l]+=value
b[r+1]-=value
区间减法改为:
b[l]-=value
b[r+1]+=value
//差分还原
for i(1-n)
a[i]=b[i]+a[i-1]
例题:大学里的树木要打药
题目
教室外有N棵树,根据不同的位置和树种,学校要对其上不同的药。因为树的排列成线性,且非常长,我们可以将它们看作一条直线给他们编号。树的编号从0~N-1且N<1e6。
对于树的药是成区间分布的,比如3-5号的树靠近下水道,所以它们要用驱蚊虫的药,20-26号的树,它们排水不好,容易涝所以要给他们促进根系的药。诸如此类,每种不同的药要花不同的钱。
现在已知共有M个这样的区间,并且给你每个区间花的钱,请问最后,这些树木花了多少药费。
输入
每组输入的第一行有两个整数N和M。N代表马路的共计多少棵树,M代表区间的数目,N和M之间用一个空格隔开。
接下来的M行每行包含三个不同的整数,用一个空格隔开,表示一个区域的起始点L和终止点R的坐标,以及花费。
输出
输出包含一行,这一行只包含一个整数,所有的花费。
解析
1.利用b[i]=a[i]-a[i-1]差分式。这里由于开始时都是0,可以用,但是用完都还是0,所以没有意义,直接跳过就可以。
2.依次读入区间的值,然后将对于区间的操作转化为对于区间端点操作加减。所以数目整体区间要右移一位。对于每个[l,r]区间的加减操作都转化为对端点l,r+1的操作。
3.差分还原。
#include<bits/stdc++.h>
using namespace std;
int b[100005],a[100005];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
b[i]=a[i]-a[i-1];
}
while(m--){
int l,r,value;
cin>>l>>r>>value;
l+=1;
r+=1;
b[l]+=value;
b[r+1]-=value;
}
long long sum=0;
for(int i=1;i<=n;i++){
a[i]=b[i]+a[i-1];
sum+=a[i];
}
cout<<sum<<endl;
return 0;
}
前缀和
前缀和也是主要用于处理区间问题。
前缀和是指序列的前n项和,可以理解为数学上的数列的前n项和。当对于某一个区间进行多次询问[l,r]的和时,如果正常处理,那么我们每次都要从[l,r],查询N次,那么时间复杂度也是平方阶的。
如果我们使用前缀和,构造一个前缀和数组,通过对端点的值的减法就能O(1)求出[l,r]的和,然后N次查询,时间复杂度就能降到O(N)。
前缀和的特点:
1.将对于区间的求和操作转化为对于端点值的减法的操作。
2.区间求和操作的时间复杂度为O(1).
3.数组存放时从1开始。
4.前缀和数组比原来的数组序列多一个数,第0个。
例题:大学里的树木要维护
题目
教室外有N棵树,根据不同的位置和树种,学校要对其上不同的药。因为树的排列成线性,且非常长,我们可以将它们看作一条直线给他们编号。树的编号从0~N-1且N<1e6。
由于已经维护了多年,每一棵树都由学校的园艺人员进行了维护费用的统计。每棵树的前期维护费用各不相同,但是由于未来需要打药,所以有些树木的维护费用太高的话,就要重新种植。由于维护费用也成区间分布,所以常常需要统计一个区间的树木的维护开销。
现在园艺想知道,某个区间内的树木维护开销是多少,共计M个区间需要查询。
输入
每组输入的第一行有两个整数N和M。N代表马路的共计多少棵树,M代表区间的数目,N和M之间用一个空格隔开。
接下来的一行,包含N个数,每个数之间用一个空格隔开。
接下来的M行,每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点L和终止点R的坐标。
输出
输出包含一行,这一行只包含一个整数,所有的花费。
解析
1.利用sum[i]=a[i]+sum[i-1]前缀和式在输入时求出前缀和。
2.依次读入区间的值,然后将对于区间的操作转化为对于区间端点操作加减。对于每个[l,r]区间的求和操作都转化为对端点[r]-[l-1]的操作。
3.输出答案。
#include<bits/stdc++.h>
using namespace std;
int a[100005],sum[100005];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=a[i]+sum[i-1];
}
while(m>0){
m-=1;
int l,r;
cin>>l>>r;
cout<<sum[r]-sum[l-1]<<endl;
}
return 0;
}