前缀和与差分
前缀和
前缀和是指前缀的总和,即对于一个存放数字的数组而言,前缀就是指这个数组的前n项,而所谓前缀和即使对这前n项求和,前缀和一般是求一个数组中连续n项数组元素的和。
一维前缀和
问题引入:
输入一个长度为n的整数序列,接下来进行m次询问,每次询问输入一对l,r。对于每次询问,你需要计算出原序列中从第l个数到第r个数的和并输出它。
解题
暴力解法
对于这道题,易知它的暴力解法就是遍历区间求和。代码如下:
#include<iostream>
using namspace std;
int a[10000];
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1 ; i <= n ; i++)
cin >> a[i];
for(; m > 0 ; m--)
{
int l , r;
int ans = 0;
cin >> l >> r;
for(int i = l ; i <= r ; i++)
ans += a[i];
cout << ans << endl;
}
return 0;
}
此时的时间复杂度约为O(n*m),如果数据量较大的时候,就很可能超时,为了减小时间成本,提高程序运行速度,就出现了前缀和算法,用以大大提升运算效率。
前缀和解法
#include<iostream>
using namspace std;
int a[10000];
long long ans[10000] = 0;
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1 ; i <= n ; i++)
{
cin >> a[i];
ans[i] = ans[i-1] + a[i];
}
for(; m > 0 ; m--)
{
int l , r;
cin >> l >> r;
cout << (ans[r] - ans[l]) << endl;
}
return 0;
}
我们定义了一个ans[10000]数组,用来存放从1到n这n个数的前缀和。此时如果我们要查询从l到r之间的数的和ans[r] 只需要用从1到r的前缀和减去从1到l的前缀和ans[l]即可。即ans[r-l] = ans[r] - ans[l];
此时就可以大大提升运行效率。
差分
差分可以看作是前缀和的逆运算
刚才的程序中我们给定了a[n],sum[n]。sum[n]是a[n]的前缀和数组,此时,a[n]就是sum[n]的差分数组。
一维差分
问题引入
输入一个长度为n的整数序列,接下来进行m次操作,每个操作包含三个整数l,r,c,表示将序列中【l,r】之间的每个数加上c。请输出进行完所有操作后的序列。
暴力解法
#include<iostream>
using namspace std;
int a[10000];
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1 ; i <= n ; i++)
cin >> a[i];
while(m--)
{
int l , r , c;
cin >> l >> r >> c ;
for(int i = l ; i <= r ; i++)
a[i] += c;
}
for(int i = 1 ; i <= n ; i++)
cout << a[i] <<" ";
return 0;
}
使用暴力做法的时间复杂度为O(n * m),下面使用差分算法。
差分解法
#include<iostream>
using namspace std;
int a[10000];
long long ans[10000] = 0;
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1 ; i <= n ; i++)
{
cin >> a[i];
ans[i] = a[i] - a[i-1];
}
while(m--)
{
int l , r , c;
cin >> l >> r >> c;
ans[l] += c;
ans[r+1] -= c;
}
cout << ans[1] << " ";
for(int i = 2 ; i <= n ; i++)
{
ans[i] += ans[i-1]; //将进行好所有操作的差分数组变为从1到n的前缀和数组
cout << ans[i] << " ";
return 0;
}
此时可以得出一维差分的时间复杂度:给a数组中的【l,r】区间内的每一个数加上c,只需要对差分数组ans做ans[l] += c,ans[r+1] -= c即可,此过程时间复杂度为O(1),大大提高了程序的运行效率。