基础算法-差分与前缀和
原创 Acm Algorithm ACM算法 4月23日
差分与前缀和
首先要说明的是这两种方法都是优化了对数组进行操作的时间复杂度,我们先来看两个问题。
问题1:已知一个数组A的长度为n(n<=1e6),问数组A从i到j这个区间所有数的和的为多少(一共有k次询问,k<=1e6)?
问题2:已知一个数组A的长度为n(n<=1e6),对数组A从i到j这个区间加或者减一个定值w(一共有k次操作,k<=1e6)。问操作后的数组中每个数的值为多少?
前缀和
对于问题1,使用暴力的时间复杂度为O(n*k),显然这样是行不通的。有没有更好的解决方式呢?答案是有的。
什么是前缀和?有两个数组arr与brr,对于任意的角标i,数组brr[i]=arr[1]+arr[2]+ … +arr[i],即:
brr[1]=arr[1];
brr[2]=arr[1]+arr[2];
brr[3]=arr[1]+arr[2]+arr[3];
…
brr[i-1]=arr[1]+arr[2]+…+arr[i-1];
brr[i]=arr[1]+arr[2]+…+arr[i-1]+arr[i];
数组arr中从i到j的所有数的和=brr[j]-brr[i-1].这样的方法叫前缀和,数组brr为数组arr的前缀和数组,并且我们通过上面的式子发现brr[i]=brr[i-1]+arr[i],所以我们同时缩短求前缀和的时间复杂度,故此时解决问题1的时间复杂度为O(n+m)
给出解决问题1的代码
#include<bits/stdc++.h>
using namespace std;
const int MI=1e6+10;
int arr[MI],brr[MI];
int main(){
int n;//即数组的长度为多少
cin>>n;//获取n
for(int i=1;i<=n;i++)//注意数据从i=1开始储存,操作方便
{
cin>>arr[i]; //获取数据
}
for(int i=1;i<=n;i++)//求前缀和
{
brr[i]=brr[i-1]+arr[i]; //这是核心代码哟
}
int k;//获取k
while(k–)
{
int a,b;//表示arr从a到b的和
cin>>a>>b;
cout<<brr[b]-brr[a-1]<<endl;
}
}
差分
对于问题2,我们假设操作的数组是上面的brr,经过分析,如果对arr[i]加上一个定值w,那么从brr[i]到brr[n]的每一个数都会加上w,然后对arr[j+1]减去w,这时brr数组相当于从i到j加上w。问题2就这样解决了,其中arr[i]=brr[i]-brr[i-1],arr为brr的差分数组。附上问题2的解题代码
#include<bits/stdc++.h>
using namespace std;
const int MI=1e6+10;
int arr[MI],brr[MI];
int main(){
int n;//即数组的长度为多少
cin>>n;//获取n
for(int i=1;i<=n;i++)//注意数据从i=1开始储存,操作方便
{
cin>>brr[i]; //获取数据
}
for(int i=1;i<=n;i++)//求差分
{
arr[i]=brr[i]+brr[i-1];
}
int k;//获取k
while(k–)
{
int a,b,w;//对于区间[a,b]的每一个数加w
cin>>a>>b>>w;
arr[a]+=w;
arr[b+1]-=w;
}
for(int i=1;i<=n;i++)
{
brr[i]=brr[i-1]+arr[i];//求arr的前缀和
cout<<brr[i]<<" ";
}
}
前缀和与差分的关系
假设数组A是数组B的前缀和数组,那么数组B就是数组A的差分数组。
前缀和是解决对数组的区间查询多,修改少的问题。
差分是解决对数组的区间修改的数据相同,并且修改次数多,但查询少的问题。
如果遇到对数组区间查询多,修改也多的情况怎么办?不着急,有一种叫做线段树的数据结构会来解决这种问题。不过需要强调的是线段树的时间复杂度为O(nlogn),而前缀和与差分的时间复杂度为O(n)。
例题 Codeforces 1343D
链接:https://codeforces.com/contest/1343/problem/D
偷偷告诉你,这是一道差分题。
关键字
差分、前缀和