数据结构 树状数组
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
二零一七年五月九日