数据结构 树状数组

Chapter 2. 数据结构  树状数组

 

Sylvia's I. 树状数组(二叉索引树).

动态连续和查询问题.给定一个数组含有n个元素的数组A,设计一个数据结构,支持以下两个操作

add(x,d):让Ax增加d.

query(L,R):计算区间[l,r]中所有元素的和

lowbit:我们定义的lowbit(x)是x的二进制表达式中最右边的1所对应的值,例如,324的二进制是101000100,所以lowbit(324)=4(二进制是100),在程序中实现是lowbit=x&(-x),-x实际上是x的按位取反,末尾加1的结果,如

324=101000100

     -324=010111100

按位取“与”后,前面的部分全部变成0,而之后的“100”不变,即lowbit(324)=4

BIT:下图是一棵典型的BIT,由13个结点组成,编号1—13,而左边的1、2、4、8是同行结点的lowbit,每一行的结点的lowbit相同

 对于结点i,如果它是左子结点,那么它的父节点编号是i+lowbit(i),如果它是右子节点,那么它的父节点是i-lowbit(i)

 

 


 
                                         

图1

 

黑色的结点是BIT中的结点,然后构造一个辅助数组C

Ci=Ai-lowbit(i)+1+Ai-lowbit(i)+2+..+Ai

换句话说,C数组的每个元素就是A数组中的一段连续和,即图中紫色+黑色的长条,对于lowbit=1的结点仅指黑色的长条,这个长条中的数的和就是Ci,比如,其中结点4的长条就是从1—4结点,即C4=A1+A2+A3+A4

那么对于add(x,d)操作,如果修改了Ai,那么需要从Ci开始往右上走,沿途修改所有包含Ai值的Ci,如下图,那么可以看作我们修改的Ci值是左子结点,所以向右上走即可以用i+lowbit(i)计算,如下图中C3-->C3+lowbit(3)=C3+1=C4-->C4+loebit(4)=C8

代码:时间复杂度O(logn

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

 

 

 而对于query(l,r)这一操作,先计算前缀和Si,从结点i向左上走,把沿途经过的Ci全部加起来,如下图,此时Ci可以看作右子结点,那么向左上走可以用i-lowbit(i)计算,然后query(l,r)=Sr-Sl-1

 

 

 

 代码:时间复杂度O(logn

int sum (int x){
    int ret=0;
    while (x>0){
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}

 

Sylvia's II . 树状数组的应用.

① 单点修改和区间求和:

已知一个数列,你需要进行下面两种操作:

1.将某一个数加上x

2.求出某区间所有数的和

操作1: 格式:1 x k 含义:将第x个数加上k

操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

输出每一个操作2后的结果

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAX 500005

int c[MAX];
int n,m,z,t,a,b;
int lowbit(int x){
    return (x&(-x));
}
int query (int x){//查询
    int ret=0;
    while (x>0){
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}
void add(int x,int y){//修改,把Ax增加y
    while (x<=n){
        c[x]+=y;
        x+=lowbit(x);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d",&z);
        add(i,z);//预处理
    }
    for (int i=1;i<=m;i++){
        scanf("%d%d%d",&t,&a,&b);
        if (t==1){
            add(a,b);//如果是1操作,那么进行修改
        }
        else {
            printf("%d\n",query(b)-query(a-1));//如果是2操作,那么输出区间[a,b]中所有元素的和,它等于前缀和Sb-Sa-1
        }
    }
    return 0;
    
}

 

②区间修改和单点求值:

已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数数加上x

2.求出某一个数的值

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

操作2: 格式:2 x 含义:输出第x个数的值

输出每一个操作2后的结果

思想:对于读入数据进行差分的预处理储存在C数组中,那么前缀和Si就是Ai.

 例如:

5 5
1 5 4 2 3
1 2 4 2
2 3
那么进行预处理后的数组C是 {1,4,-1,-2,1}
读入 1 2 4 2 那么将A数组区间[2,4]都加上2,对于C数组,进行add(2,2)和add(5,-2),C数组变成{1,6,-1,-2,-1},那么数组A(代码中未使用)会变成{1,7,6,4,3},这就实现了区间[2,4]的数值增加.
读入 2 3 那么进行query(3),求C3的前缀和就是6也就是A数组中的数值

 

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAX 500005

int c[MAX];
int n,m,z,t,pre=0,a,b,d;
int lowbit(int x){
    return (x&(-x));
}
int query (int x){//查询
    int ret=0;
    while (x>0){
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}
void add(int x,int y){//修改,把Ax增加y
    while (x<=n){
        c[x]+=y;
        x+=lowbit(x);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d",&z);
        add(i,z-pre);//使用差分
        pre=z;
    }
    for (int i=1;i<=m;i++){
        scanf("%d",&t);
        if (t==1){
            scanf("%d%d%d",&a,&b,&d);
            add(a,d);//对于差分后的数组区间第一个元素加上d,那么对于原数组在第一个元素之后的每一个值都增加了d
            add(b+1,-d);//所以将区间之后的第一个元素减掉d,那么区间后的元素不受其影响,最终对于原数组只有区间中的每一个元素都增加了d
        }
        else {
            scanf("%d",&a);
            printf("%d\n",query(a));//差分后的数组c的前缀和Sa就是原数组Aa的值,直接输出
            
        }
    }
    return 0;
}

 

 

 

 

 

 


 

鱼丽之宴

木心

很多人的失落

是违背了自己少年时的立志

自以为成熟,自以为练达,自以为精明

从前多幼稚

总算看透了,想穿了

于是

我们从此变成了自己年少时最憎恶的那种人

 


 

Sylvia

二零一七年五月九日

 

 

 

posted @ 2017-05-09 21:30  Sylvia_lee  阅读(140)  评论(0编辑  收藏  举报