树状数组(树状数组的基本用法与操作)
什么是树状数组?树状数组简单的来说就是将一个数组模拟树形结构。
树状数组有什么用?树状数组可以将求和的操作从O(n)操作简化为O(logn)。
如图所示,横线下方为a数组表示为初试数据;上方为数组c,利用树形结构存储a数组内的数据。我们列举出来的这些:
c[1]=a[1]
c[2]=a[1]+a[2]
c[3]=a[3]
c[4]=a[1]+a[2]+a[3]+a[4]
c[5]=a[5]
c[6]=a[5]+a[6]
c[7]=a[7]
c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]
......
当我们列举出来这些数据的时候,我们发现:
①所有下标为奇数的c数组都为对应的a数组的值。
②所有下标为2的k次幂的c数组都为对应a数组2的k次幂的前缀和。
③其它下标为偶数的c数组都是由该偶数前所有的值的和,减去该下标对应的最大的2的k次幂的值的和。
即c[i] =a[i - +1] + a[i -+2] + ... + a[i]; //k为i的二进制中从最低位到高位连续零的长度(该偶数的2进制数的最低位1的位置再-1。
当我们计算7的前缀和,即我们需要计算sum=c[7]+c[6]+c[4];
即我们可以拓展为SUM = C[i] + C[i-] + C[(i - ) - ] + .....;//k1k为i的二进制中从最低位到高位连续零的长度,k2为i-后从最低位到高位连续零的长度。
我们引入lowbit(x)函数,此函数操作为x&(-x)。这里的-x在计算机中是以补码的形式计算的。此函数的作用为:当x为0时,返回为0;当x为奇数时,返回1;当x为偶数时,且为2的m次方时,返回x;当x为偶数,却不为2的m次方的形式时,返回,其中k为二进制中从最低位到高位连续零的长度(该偶数的2进制数的最低位1的位置再-1)。
注意事项:树状数组能够有效的解决单点更新,区间查询的过程。
题目引入:树状数组
代码:
#include<iostream>
#include <stdio.h>
#include<string.h>
using namespace std;
int a[1000001];
int c[1000001];
int lowbit(int i){return i&(-i);
}
void update(int i,int x,int n){while(i<=n){c[i]+=x;i+=lowbit(i);}
}
int downdate(int i){int sum=0;while(i>0){sum+=c[i];i-=lowbit(i);}return sum;
}
int main(){int n,m;cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];update(i,a[i],n);}int x,y,z;for(int i=1;i<=m;i++){cin>>x>>y>>z;if(x==1)update(y,z,n);else cout<<downdate(z)-downdate(y-1)<<endl;}return 0;
}
区间更新,单点查询
我们使用差分数组来表示,即D[i]=a[i]-a[i-1](i>=1);//建议看这里,有比较详细的介绍和说明
区间更新:
例如我们需要更新区间[2,6],将[2,6]区间内的每一个都加上x,x可正可负;
则:
D[2]=(a[2]+x)-(a[1]) =a[2]-a[1]+x=D[2]+x
D[3]=(a[3]+x)-(a[2]+x)=a[3]-a[2] =D[3]
D[4]=(a[4]+x)-(a[3]+x)=a[4]-a[3] =D[4]
D[5]=(a[5]+x)-(a[4]+x)=a[5]-a[4] =D[5]
D[6]=(a[6]+x)-(a[5]+x)=a[6]-a[5] =D[6]
D[7]=(a[7]) -(a[6]+x)=a[7]-a[6]-x=D[7]-x
即若更新[2,6],将区间[2,6]区间内的每一个都加上x,则就相当于D[2]+x,D[7]-x。
我们推广到一般情况,如果更新区间[a,b],且将[a,b]区间内加上x,则相当于D[a]+x,D[b+1]-x。
单点查询:
若想查询a[n],则根据累加法:
a[n]=D[n]+a[n-1]
a[n-1]=D[n-1]+a[n-2]
...
a[3]=D[3]+a[2]
a[2]=D[2]+a[1]
a[1]=D[1]+a[0]
a[0]=0
则
这种情况我们则需要用树状数组了。
即用D数组来建立一个树状数组。
区间更新,区间查询
对于求[1,n]区间内的总和sum,我们有如下处理:
已知则
=n*(D[1]+D[2]+...+D[n])-(0*D[1]+2*D[2]+...+(n-1)*D[n])
=
这样我们得到了两个,即可用于树状数组。
我们令c数组为(i-1)*D[i]。
所以,我们设置两个树状数组sum1[i]<-D[i]和sum2[i]<-c[i]。
区间更新:
若更新区间[a,b],并且在区间同时+x,对于sum1,我们可以在a处向上更新x,在b+1处更新-x;对于sum2,我们可以在a处向上更新(a-1)*x在b+1处向上更新b*-x;
区间查询:
若查询区间[l,r],我们可以找到区间[0,l-1]的值,向下查找sum1-sum2,找到[0,r],向下查找sum1-sum2;
代码:
#include<bits/stdc++.h>
int n,m;
int a[50005] = {0};
int sum1[50005]; //(D[1] + D[2] + ... + D[n])
int sum2[50005]; //(1*D[1] + 2*D[2] + ... + n*D[n])
int lowbit(int x){return x&(-x);
}
void updata(int i,int k){int x = i; //因为x不变,所以得先保存i值while(i <= n){sum1[i] += k;//更新sum1sum2[i] += k * (x-1);//更新sum2i += lowbit(i);}
}
int getsum(int i){ //求前缀和int res = 0, x = i;while(i > 0){res += x * sum1[i] - sum2[i];//这里因为sum1没有乘以n,所以在出结果时成i -= lowbit(i);}return res;
}
int main(){cin>>n;for(int i = 1; i <= n; i++){cin>>a[i];updata(i,a[i] - a[i-1]); //输入初值的时候,也相当于更新了值}//[x,y]区间内加上kupdata(x,k); //A[x] - A[x-1]增加kupdata(y+1,-k); //A[y+1] - A[y]减少k//求[x,y]区间和int sum = getsum(y) - getsum(x-1);return 0;
}
当然,对于区间查询,区间更新的问题,我么还可以使用线段树来处理。
树状数组求逆序对数
我们求逆序对数除了归并算法求,还可以树状数组求逆序对数。
步骤:
- 离散化:这里所谓离散化就是将一个序列的相对大小表示出来。目的是为了方便数据存储。对于重复的数据,我们就依据数据的先后顺序来处理
例如序列:6 25 9 63 2
序列的离散化结果为2 4 3 5 1
例如序列5 60 5 3 2 3
序列的离散化结果为4 6 5 2 1 3
- 树状数组求逆序对数:
我们怎样求逆序对数呢?
我们可以依次从后向前遍历,以遍历的时间为序列的顺序,向前查找并且更新。
例如此序列逆序为1 5 3 4 2,则第一次寻找1前面的数据有多少比1小(向下寻找),然后把1向上更新+1;第二次寻找5前面的数据,发现c[4]=1,即5前面有1个比5小的数,然后将5向上更新+1。
题目引入:树状数组求逆序对
代码:
#include<bits/stdc++.h>
using namespace std;
struct node{long long sum;long long j;long long k;
};
long long sum=0;
node a[50000005];
long long c[50000005];
long long lowbit(long long x){return x&(-x);
}
bool cmp(const node xx,const node yy){if(xx.sum==yy.sum&&xx.j<yy.j)return true;if(xx.sum<yy.sum)return true;else return false;
}
bool cmp1(const node xx,const node yy){if(xx.j<yy.j)return true;else return false;
}
int main(){long long n;cin>>n;for(long long i=1;i<=n;i++){cin>>a[i].sum;a[i].j=i;}sort(a+1,a+1+n,cmp);for(long long i=1,x=1;i<=n;i++){a[i].k=x;x++;}sort(a+1,a+1+n,cmp1);for(long long i=n;i>=1;i--){long long x=a[i].k;long long y=x;while(y>0){sum+=c[y];y-=lowbit(y); }while(x<=n){c[x]++;x+=lowbit(x);}}cout<<sum;return 0;
}
树状数组(树状数组的基本用法与操作)相关推荐
- 吊打线段树的超级树状数组
你是否讨厌线段树那冗长的代码?你是否还在因为线段树的难调试而满头♂dark汗?那么,请不要错过!超级树状数组特价!只要998,只要998! ##¥--#--¥%--&%¥--ER#%$#$#^ ...
- BZOJ1103 大都市 DFS序 树状数组维护差分数组
BZOJ1103 大都市 问题描述 在经济全球化浪潮的影响下,习惯于漫步在清晨的乡间小路的邮递员Blue Mary也开始骑着摩托车传递邮件了. 不过,她经常回忆起以前在乡间漫步的情景.昔日,乡下有依次 ...
- Codeforces Round #439 (Div. 2) E. The Untended Antiquity 二维线段树||二维树状数组
http://codeforces.com/contest/869/problem/E 题意:n*m的矩阵,q次操作,三种类型 类型1:给指定矩阵加上围栏 类型2:给指定矩阵去掉围栏 类型3:查询两点 ...
- 树形json扁平化,一维数组树状化,对象深拷贝,元素后插入新元素,格式或动态路由等常用js合集
索引 一.在元素后面插入一个新的元素. 二.对象或者数组的深拷贝. 三.从服务器端获取到动态路由表的格式化. 四.json树形数据扁平化处理(变成一维数组) 五.一维数组转化为树状结构对象. 六.防抖 ...
- Acwing 4339 敌兵布阵 暴力 + 分块 + 线段树 + Zkw线段树 + 树状数组
来一篇超全题解 数据结构大杂烩 原题连接 题目描述 敌人有 NNN 个工兵营地,编号 1∼N1∼N1∼N. 初始时,第 iii 个营地有 aia_iai 个人. 接下来有若干个命令,命令有 444 ...
- 树型结构——树状数组
目录 1.树状数组的引入 2.基本操作:主要包括 插入操作,查询操作. 3.具体实现: 例题:1.楼兰图腾 241. 楼兰图腾 2.一个简单的整数问题 3.一个简单的整数问题2 1.树状数组的引入 树 ...
- (js)扁平数组树状化 树状数组扁平化
扁平数组树状化(利用递归,两个函数完成树状转化 ) 数组格式: let list = [{ id: 1, title: '标题1', p_id: 0 },{ id: 2, title: '标题2', ...
- 数组树/fenwicktree/Binary Indexed Tree
在解类似 leetcode 307题区域和检索 - 数组可修改的题时,我们可以使用一种比较小众的数据结构,数组树. 数组树的结构依托于数组,它的结构看起来类似下面这种: 1,2,3....代表他们在数 ...
- 庖丁解牛获取连接状态数的awk数组命令
全部系列分为五篇文章,本博文为第二篇: 三.庖丁解牛获取连接状态数的命令 3.1 获取连接状态数的awk命令 netstat -n |awk '/^tcp/ {++S[$NF]} END {for(a ...
- 2022年3月24日蜻蜓q旗舰版v1.2.2更新-详细的用户,内容,整体圆饼+树状+横条状统计展示功能
2022年3月24日蜻蜓q旗舰版v1.2.2更新-详细的用户,内容,整体圆饼+树状+横条状统计展示功能 更新日志 ·增加用户统计,用户画像,区分男性女性,月度统计,年度统计,注册用户数统计. ·增加评 ...
最新文章
- Leetcode 4.28 Tree Easy
- 跨源资源共享(CORS)漏洞修复
- 可变化的鸿蒙武器,DNF2018史诗改版大全 武器套装改版属性介绍
- Fetion2008 分析 Part1:准备工作
- STC芯片在Keil中的添加与使用
- xshell上传文件插件lrzsz
- 线程的虚假唤醒(Spurious Wakeups)以及解决方案
- Springboot系列之RestApi中获取枚举类属性的几种方式
- 部署nodejs报No package nodejs available
- Discuz最全的常见问题及故障整理
- SOCK_NONBLOCK,accept4 阻塞与非阻塞SOCKET
- 模电学习心得(转载)_史蒂文森sun_新浪博客
- C语言程序设计——猜数字游戏
- 3d建模网上学习靠谱吗?学3d建模哪个学校好?
- 【asm基础】nasm和masm的一些区别
- 数据库的基本操作(一)
- mac 部署nexus私服库
- seajs 的api接口
- Luogu P5037 抓捕
- 电子秤c语言编程,基于AT89C51的数字电子秤的设计最终版(样例3)