树状数组总结

来源:互联网 发布:淘宝客服在线咨询 编辑:程序博客网 时间:2024/06/05 04:07

  • 树状数组基本用法
  • 什么是树状数组数学解释
  • 一插点问线单点增减区间查询
  • 二插线问点区间修改单点查询 区间每次修改的值是一样的
  • 三 区间查询区间修改

树状数组基本用法:

一、插点问线,单点增减+区间查询 ,如“士兵杀敌(二)”

二、插线问点,区间修改+单点查询 如“士兵杀敌(四)”(区间每次修改的值是一样的)

三、 区间查询+区间修改

四、 树状数组求逆序数

五、 多维树状数组

一般的用数组数组来解的题,都是不用a[0]的,也就是元素是从a[1]~a[n],因为 sum[n~m]=sum[m]-sum[n-1],避免 n-1 为负数。

什么是树状数组?(数学解释)

参见博客搞懂树状数组

这里写图片描述
我们只需记住:
1. 我们原始数组是A[],但是我们通过A[]构建了树状数组C[],以后其实都是对C[]进行操作,几乎用不到数组A[]了。
2. 有 m=k&(-k) ,则m表示的就是k最后面一个’1’的值。比如k=110,则m=10 即是2。k=1001,则m=1 即是1。
3. 定义的C[i]表示从A[i]开始向左共i&(-i)个A的和。
4. 有C下标为k, 则k+=k&(-k) ,更新的k 就是原来k的父节点
5. 有C下标为k, 则k-=k&(-k) ,更新的k 一定是原来k左边子树的根节点。比如无论k=5,还是k=7, k-=k&(-k)得到的 k 一定等于4;如果k=7,则k-=k&(-k)得到的k一定是6。
以上结论可以从上图中的下标找到规律(也可以用数学来证明 参见博客搞懂树状数组

一、插点问线,单点增减+区间查询

例题可参见nyoj 的“士兵杀敌(二)”
1. 构建C[]:

    *   由于C[i]表示从A[i]开始向左共i&(-i)个A的和。所以for循环直接累加即可    *   可以认为是建树(创建C[]),就是在不断的更新点。

2. 单点增减方法: 比如对A[k]加上num,那么从C[k]开始,迭代找到他的父节点k+=k&(-k),c[k]+=num;
3. 区间和查询方法:比如求A[i]~A[j]的和,结果就是sum[j]-sum[i-1]。sum[i]表示A[]从1到i的和,那么怎么求sum[i]呢?
只需从i开始向左依次找到遇到的子树的根C[i -=i&(-i)] ,全都加起来就好了。
代码,或者其他的理解,参见博客搞懂树状数组

二、插线问点,区间修改+单点查询 (区间每次修改的值是一样的)

例题可参见nyoj 的“士兵杀敌(四)”
创建博客 :http://blog.csdn.net/qq845579063/article/details/52097782?locationNum=2&fps=1#t1


以下内容为对上面博客的简单修改

*

改段求点要求数组A原来值都为0,且区间每次修改的值是一样的。

比如有一个数组 a = [ 0, 0, 0, 0, 0, 0, 0, 0, 0],每次的修改都是一段,比如让
a[1]~a[5]中每个元素都加上10,让 a[6]~a[9] 中每个元素都减去2,求任意的元素的值。
跟改点求段不同,这里要转变一个思想。在改点求段中,C[i]表示Ci节点所管辖的子节点的元素和,而在改段求点中,C[i]表示Ci所管辖子节点的批量统一增量。

这里写图片描述

C8管辖A1~A8这8个节点,如果A1~A8每个都染色一次,因为前面说了C[i]表示i所管辖子节点的统一增量,那么也就是 C[8]+=1
注意:我们只改管辖子节点都修改的子树的根C, 比方说A5~A7都染色两次,也就是 C[6] +=2, C[7] +=2
,而不改C[8],也不改C[5]

,C8管辖A1~A8这8个节点,如果A1~A8每个加1,因为前面说了C[i]表示i所管辖子节点的统一增量,那么也就是
C[8]+=1,但是如果是A3~A7每个都加1 ,那么不能够直接改C[8],只能够改C[7],C[6],C[4].
但是如果是A2~A8每个都都加1
,也不能这样弄了,因为A[2]直接指向C[2],由于没有改A[1],我们就不能够改C[2],怎么办?方法是先A[1~8]都增加加1,再让C[1],单独的减一。

如果要求A1被染色的次数,C8是能管辖到A1的,也就是说C[8]的值和A1被染色的次数有关,仔细想想,也就是把能管辖到A1的父节点的sum值累积起来即可。
以下的sum就是上面说的C .

综上所述:
1. 求树状数组C[]
跟改点求段不同,这里要转变一个思想。在改点求段中,C[i]表示Ci节点所管辖的子节点的元素和,而在改段求点中,C[i]表示Ci所管辖子节点的批量统一增量。
同时对于改段求点问题,一般要求A[]初始为0,则C[]初始为0。这样就只剩下两个问题 : 区间修改的更新 和 单点的查询。
2. 区间修改的更新
假设将A的x~y区间都加上了2 , 那么将1~y区间的增量加2(就是1~y所有子树的根,他们的下标就是k=y; k-=k&(-k)) ;再将1~(x-1)的增量减2。(就是1~(x-1)所有子树的根,他们的下标就是k=x-1; k-=k&(-k))
3. 单点i的查询
就是将该点的父节点,父节点的父节点,父节点的父节点的父节点… … (k+=k&(-k))的增量加起来,再假设A[i],但是A[i]为0,所以不用加。

int sum[N], n;//////////////////////////////////////////////////////////lowbitint lowbit(int x) { return x & (-x); }/////////////////////////////////////////////////////区域更新void update(int index, int val) {  while (index) {    sum[index] += val;    index -= lowbit(index);  }} scanf("%d%d", &x, &y);//对下标x~y的A都加1 update(y, 1); //1到y都加1 update(x - 1, -1);//1~x-1的C都减1 ,这样就保证了x~y都加1//////////////////////////////////////////////////////////查询单节点,迭代,从i开始不断加上其父节点的值C,父节点坐标i+=i&(-k)int query(int index) {  int ans = 0;  while (index <= n) {    ans += sum[index];    index += lowbit(index);  }  return ans;}  printf("%d ", query(i));//查询下标为i的A的值

三、 区间查询+区间修改

参考博客
http://blog.csdn.net/a569329637/article/details/10005979
以下内容为上面博客的简单修改:
把问题转化为求数组的前缀和。
1. 更新函数update(s, t, d)

 首先,看更新操作update(s, t, d)把区间A[s]...A[t]都增加d,我们引入一个数组delta[i],表示A[i]...A[n]的共同增量,n是数组的大小。那么update操作可以转化为:1)令delta[s] = delta[s] + d,表示将A[s]...A[n]同时增加d,但这样A[t+1]...A[n]就多加了d,所以2)再令delta[t+1] = delta[t+1] - d,表示将A[t+1]...A[n]同时减d

2. 查询操作query(s, t)

   然后来看查询操作query(s, t),求A[s]...A[t]的区间和,转化为求前缀和,设sum[i] = A[1]+...+A[i],则                            A[s]+...+A[t] = sum[t] - sum[s-1],那么前缀和sum[x]又如何求呢?

3. 前缀和sum[x]的求解
它由两部分组成,一是数组的原始和A,二是该区间内的累计增量和, 把数组A的原始值保存在数组org中,并且delta[i]对sum[x]的贡献值就是数组在i~x之间的增量和,1~x长度为x-i+1 ,所以贡献值为delta[i]*(x+1-i),那么

sum[x] = org[1]+…+org[x] + delta[1]x + delta[2](x-1) + delta[3]*(x-2)+…+delta[x]*1
= org[1]+…+org[x] + segma(delta[i]*(x+1-i))
= segma(org[i]) + (x+1)*segma(delta[i]) -segma(delta[i]*i),1 <= i <= x
这里segma(B[i]) 表示数组B的前i项和
这其实就是三个数组org[i], delta[i]和delta[i]*i的前缀和,org[i]的前缀和保持不变(因为我们一直保持数组A的值不变,一直在改变的是数组delta),事先就可以求出来,delta[i] 和 delta[i]*i 的前缀和是不断变化的,可以用两个树状数组来维护。

所以我们只需要建立两个树状数组 delta[] 和 delatai[] 。这两个树状数组的初始值都为0。所以只剩下两个操作: 修改 和 查询。

注意: 由于我们要求得是delta[i]和delta[i]*i的前缀和,那么构建的两个树状数组 delta[] 和 delatai[] 应该是 单点增减+区间查询(方式一) 的方式,而不是区间增减+ 单点查询 (方式二)。下面我们讲解为什么?
我们可以这样理解。对于区间增减+ 区间查询,我们每更改一个区间,就相当于更改了一个delta[i]值。也就是虽然A改变的区间,但是delta和deltai改变的仅仅是单个值,而我们现在要求的是delta和deltai的前缀和,而不是A的单个值,所以我们用 单点增减+区间查询(方式一) 的方式,而不用区间增减+ 单点查询 (方式二)。
即:
对于方式二: A是下面的基础 , C(或者 delta) 是构成的树
对于这个问题: delta是下面的基础 , 再对delta构建树状数组C
所以用方式一。

  1. 修改:
    方式一的思想
    定义d是delta构建的树状数组
    当A[i~j] 加 3,delta[i] 和 delta[j+1] 发生改变,那么就对树状数组d修改两次,
    方式就是从 d的 i 开始,d[i]+=3,d[i]父节点+=3,d[i]父节点的父节点+=3,d[i]父节点的父节点的父节点+=3… …. 即下标变化为i+=i&(-i) ;
    然后从 d 的j+1开始,d[j+1]-=3, 父节点-=3,父节点的父节点-=3,父节点的父节点的父节点-=3… …. 即下标k=j+1,k+=k&(-k) ;

    同理delta*i构成的树状数组di的更新也是这样。

  2. 求delta的前i项和
    就是把树状数组d 下标i之前的子树的根的值加起来就是了,下标i之前的子树的根的下标变化就是 i-=i&(-i)。

LL a[maxn], d[maxn], di[maxn], sum[maxn], n;  memset(d, 0, sizeof(d));  memset(di, 0, sizeof(di));  ////////////////////////////////////////////////////////A的前i项和for (int i=1; i<=n; ++i)          scanf("%I64d", &a[i]);    sum[0] = 0;    for (int i=1; i<=n; ++i)          sum[i] = sum[i-1] + a[i];/////////////////////////////////////////////////////////树状数组d和di更新void Add(LL a[], LL x, LL d)  {        while (x<=n)        {              a[x] += d;              x += lowbit(x);        }  }    scanf("%I64d%I64d%I64d", &s, &t, &val);//对A的s~t都加上val      Add(d, s, val); //delta[s]=val    Add(d, t+1, -val);  //delta[t+1]=-val     Add(di, s, s*val);  //deltai[s]=i*val i是s    Add(di, t+1, -val*(t+1));  //deltai[s]=-i*val i是t+1////////////////////////////////////////////////////////////////树状数组d和di的前x项和LL Sum(LL a[], LL x)  {        LL sum = 0;        while (x>0)        {              sum += a[x];              x -= lowbit(x);        }        return sum;  }  scanf("%I64d%I64d", &s, &t);  //整体的前i项和segma(org[i]) + (x+1)*segma(delta[i]) -segma(delta[i]*i) LL sum_a = sum[t] + (t+1)*Sum(d, t) - Sum(di, t);   LL sum_b = sum[s-1] + s*Sum(d, s-1) - Sum(di, s-1);   printf("%I64d\n", sum_a-sum_b); 

http://blog.csdn.net/a569329637/article/details/10005979
http://blog.csdn.net/qq_21841245/article/details/43956633
http://blog.csdn.net/qq845579063/article/details/52097782?locationNum=2&fps=1#t1

代码收集:
http://blog.csdn.net/lawrence_jang/article/details/8054173

0 0
原创粉丝点击