[树状数组] 区间求和的三种模型

树状数组在区间求和问题上有很高的效率,尤其在非常困难的比赛中(数据量大,对时间限制很严格的比赛)能发挥非常大的作用,其各种复杂度都要比线段树低很多,而且其代码简洁优美……(好了我吹不下去了,赶紧开始)有关区间求和,主要有以下三个模型:
(以下设a[1…n]为一个长为n的序列,初始值全为0)

1、改点求段型:即对于序列A有以下操作:

  • 求整段区间的和:给定i,计算a1+a2+…+ai
  • 修改单点:给定x和c,执行ax += c

这是最容易的模型,使用bit[]树状数组就可以得到该答案:树状数组中不断进行前驱赋值就可以得到整个区间的和,对每个树状数组进行后继赋值就可以修改单点,复杂度都为log(n)。

//改点求段型
//原数组为a[1...n],树状数组为bit[1...n]
//操作【1】:ADD(x, c);  对a[x]加上c
//操作【2】:SUM(r)-SUM(l-1)。 计算sum(l,r)
int sum(int i)
{
    int s = 0;
    while(i>0)
    {
        s += bit[i];
        i -= lowbit(i);
    }
    return s;
}

void add(int i,int x)
{
    while(i<=n)
    {
        bit[i] += x;
        i += lowbit(i);
    }
}

2、改段求点型:对于序列a有以下操作:

  • 修改操作:将a[l…r]之间的全部元素值全部加上c。
  • 求和操作:求此时a[x]的值。
    这个模型中需要设置一个辅助数组b[]:b[i]表示A[1..i]到目前为止共被整体加了多少(或者可以说成,到目前为止的所有ADD(i, c)操作中c的总和)。
    则可以发现,对于之前的所有ADD(x, c)操作,当且仅当x>=i时,该操作会对A[i]的值造成影响(将A[i]加上c),又由于初始A[i]=0,所以有A[i] = B[i..N]之和!而ADD(i, c)(将A[1..i]整体加上c),将B[i]加上c即可——只要对B数组进行操作就行了。

【首先对于每个数A定义集合up(A)表示{A, A+lowestbit(A), A+lowestbit(A)+lowestbit(A+lowestbit(A))…} 定义集合down(A)表示{A, A-lowestbit(A), A-lowestbit(A)-lowestbit(A-lowestbit(A)) … , 0}。可以发现对于任何A

//改段求点型
//a[1...n]为原数组,b[1...n]为辅助数组
//操作【1】:ADD(l-1, -c); ADD(r, c); 将a[l...r]之间的全部元素+c
//操作【2】:SUM(x)。 求此时a[x]的值
void ADD(int x, int c)
{
     for (int i=x; i>0; i-=i&(-i)) 
         b[i] += c;
}
int SUM(int x)
{
    int s = 0;
    for (int i=x; i<=n; i+=i&(-i)) 
        s += b[i];
    return s;
}

3、改段求段型
【1】修改操作:将A[l..r]之间的全部元素值加上c;
【2】求和操作:求此时A[l..r]的和。

这是最复杂的模型,需要两个辅助数组:B[i]表示a[1..i]到目前为止共被整体加了多少(和模型2中的一样),C[i]表示a[1..i]到目前为止共被整体加了多少的总和(或者说,C[i]=B[i]*i)。

对于ADD(x, c),只要将B[x]加上c,同时C[x]加上c*x即可(根据C[x]和B[x]间的关系可得);

而ADD(x, c)操作是这样影响a[1..i]的和的:若x小于i,则会将a[1..i]的和加上x*c,否则(x>=i)会将a[1..i]的和加上i*c。也就是,a[1..i]之和 = B[i..N]之和 * i + C[1..i-1]之和。
这样对于B和C两个数组而言就变成了“改点求段”(不过B是求后缀和而C是求前缀和)。
另外,该模型中需要特别注意越界问题,即x=0时不能执行SUM_B操作和ADD_C操作。

//改段求段型:
//【1】修改操作:将A[l..r]之间的全部元素值加上c;
//ADD_B(r, c); ADD_C(r, c);
//if (l > 1) {ADD_B(l - 1, -c); ADD_C(l - 1, -c);}
//【2】求和操作:求此时A[l..r]的和。
//SUM(r) - SUM(l - 1)。
void ADD_B(int x, int c)
{
     for (int i=x; i>0; i-=i&(-i)) B[i] += c;
}
void ADD_C(int x, int c)
{
     for (int i=x; i<=n; i+=i&(-i)) C[i] += x * c;
}
int SUM_B(int x)
{
    int s = 0;
    for (int i=x; i<=n; i+=i&(-i)) s += B[i];
    return s;
}
int SUM_C(int x)
{
    int s = 0;
    for (int i=x; i>0; i-=i&(-i)) s += C[i];
    return s;
}
inline int SUM(int x)
{
    if (x) return SUM_B(x) * x + SUM_C(x - 1); else return 0;
}

你可能感兴趣的:(1,数据结构,————ACM训练————)