一维前缀和与差分数组

本篇仅讨论一维前缀和。

问题引入

给定n个数,完成下列操作:

  • [1] 给定L和R,求区间[L, R]的和;
  • [2] 给定L和R,有m个操作,每次操作都将[L, R]内的所有数增加z,接下来给定x和y,有q个询问,对于每次询问求出[x, y]的区间和

前缀和

前缀和是一种预处理,可以降低时间复杂度,可以在后面的计算中可以直接应用前面已经算出的结果。

对于一个给定的数列a,其前缀和数列s可由递推求得:
s [ i ] = ∑ j = 1 i a [ j ] s[i]=\sum_{j=1}^i a[j] s[i]=j=1ia[j]

int a[maxn], s[maxn];
for(int i=1; i<=n; i++)
{
	cin>>a[i];
	s[i] = s[i-1]+a[i];
}

部分和

部分和是数列a中某个下标区间内的数的和,可以表示为前缀和相减的形式:
s u m [ L , R ] = ∑ i = L R a [ i ] = s [ R ] − s [ L − 1 ] sum[L, R] = \sum_{i=L}^Ra[i] = s[R]-s[L-1] sum[L,R]=i=LRa[i]=s[R]s[L1]
所以,针对[1],当询问区间[L, R]的和时,只需计算s[R]-s[L-1]即可。

差分

针对[2],直接使用前缀和无法快速满足操作,因此引入差分数组。

差分数组可以在O(1)时间内进行区间[L, R]的整体加减某一个数。

前缀和运算与差分运算为互逆运算,任意一个数列a的前缀和数组的差分数组是它本身:
d [ i ] = { a [ i ] , if  i  = 0 a [ i ] − a [ i − 1 ] , if  i  ≥ 1 d[i]= \begin{cases} a[i], & \text {if $i$ = 0} \\ a[i]-a[i-1], & \text{if $i$ ≥ 1} \end{cases} d[i]={a[i],a[i]a[i1],if i = 0if i ≥ 1

换句话说,想要还原成原来的数组,只需要将差分数组做一遍前缀和即可。

int d[maxn];
for(int i=1; i<=n; i++)
	d[i] = a[i]-a[i-1];

接下来完成[2]:

我们已经有差分数组d,我们再定义数组d的前缀和数组f,数组f的前缀和数组sum。
d [ i ] = { a [ i ] , if  i  = 0 a [ i ] − a [ i − 1 ] , if  i  ≥ 1 d[i]= \begin{cases} a[i], & \text {if $i$ = 0} \\ a[i]-a[i-1], & \text{if $i$ ≥ 1} \end{cases} d[i]={a[i],a[i]a[i1],if i = 0if i ≥ 1

f [ i ] = { d [ i ] , if  i  = 0 f [ i − 1 ] + d [ i ] , if  i  ≥ 1 f[i]= \begin{cases} d[i], & \text {if $i$ = 0} \\ f[i-1]+d[i], & \text{if $i$ ≥ 1} \end{cases} f[i]={d[i],f[i1]+d[i],if i = 0if i ≥ 1

s u m [ i ] = { f [ i ] , if  i  = 0 s u m [ i − 1 ] + f [ i ] , if  i  ≥ 1 sum[i]= \begin{cases} f[i], & \text {if $i$ = 0} \\ sum[i-1]+f[i], & \text{if $i$ ≥ 1} \end{cases} sum[i]={f[i],sum[i1]+f[i],if i = 0if i ≥ 1

由上述公式容易得到:
s u m [ y ] − s u m [ x − 1 ] = ∑ i = L R a [ i ] sum[y]-sum[x-1] = \sum_{i=L}^Ra[i] sum[y]sum[x1]=i=LRa[i]
举个简单例子:

d[1] = a[1] f[1] = d[1] = a[1] sum[1] = f[1] = d[1] = a[1]
d[2] = a[2]-a[1] f[2] = f[1]+d[2] = a[1]+a[2]-a[1] = a[2] sum[2] = sum[1]+f[2] = a[1]+a[2]
d[3] = a[3]-a[2] f[3] = f[2]+d[3] = a[2]+a[3]-a[2] = a[3] sum[3] = sum[2]+f[3] = a[1]+a[2]+a[3]
d[4] = a[4]-a[3] f[4] = f[3]+d[4] = a[3]+a[4]-a[3] = a[4] sum[4] = sum[3]+f[4] = a[1]+a[2]+a[3]+a[4]

由此我们也可以看出,任意一个数组a的前缀和数组d的差分数组f是它本身。

那么对于每一个操作m,只需使d[x]加z,d[y+1]减z即可。

继续上面的例子:

d[1] = a[1]+z f[1] = d[1] = a[1]+z sum[1] = f[1] = a[1]+1*z
d[2] = a[2]-a[1] f[2] = f[1]+d[2] = a[1]+z+a[2]-a[1] = a[2]+z sum[2] = sum[1]+f[2] = a[1]+a[2]+2*z
d[3] = a[3]-a[2] f[3] = f[2]+d[3] = a[2]+z+a[3]-a[2] = a[3]+z sum[3] = sum[2]+f[3] = a[1]+a[2]+a[3]+3*z
d[4] = a[4]-a[3] f[4] = f[3]+d[4] = a[3]+z+a[4]-a[3] = a[4]+z sum[4] = sum[3]+f[4] = a[1]+a[2]+a[3]+a[4]+4*z

以此类推即可。

int n, m, q;
int a[maxn], d[maxn], f[maxn], sum[maxn];
cin>>n;
for(int i=1; i<=n; i++)
{
	cin>>a[i];
	d[i] = a[i]-a[i-1];
}
cin>>m;
for(int i=1; i<=m; i++)
{
	int L, R, z;
	cin>>L>>R>>z;
	d[L] += z;
	d[R+1] -= z;
}
for(int i=1; i<=n; i++)
{
	f[i] = f[i-1]+d[i];
	sum[i] = sum[i-1]+f[i];
}
cin>>q;
for(int i=1; i<=q; i++)
{
	int x, y;
	cin>>x>>y;
	cout<<sum[y]-sum[x-1]<<endl;
}

例题

51Nod - 1081 子段求和

//#pragma GCC optimize(2)
#include
using namespace std;
int n, q, a[50050];
long long s[50050];
int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        cin>>a[i];
        s[i] = s[i-1]+a[i];
    }
    cin>>q;
    while(q--)
    {
        int i, l;
        cin>>i>>l;
        cout<<s[i+l-1]-s[i-1]<<endl;
    }
    return 0;
}

HDU - 1556 Color the ball

#include
#include
#include
#include
#include
#include
using namespace std;
int n, a, b;
int bl[100010];
int main()
{
    //ios::sync_with_stdio(false);
    while(cin>>n)
    {
        if(!n) return 0;
        fill(bl, bl+100010, 0);
        //while(n--)
        for(int i=1; i<=n; i++)
        {
            cin>>a>>b;
            bl[a]++;
            bl[b+1]--;
        }
        for(int i=1; i<=n; i++)
        {
            bl[i] += bl[i-1];
            if(i==n) cout<<bl[i]<<endl;
            else cout<<bl[i]<<" ";
        }
    }
}

你可能感兴趣的:(前缀和)