(家人这是我在其他地方借鉴的,真的真的感觉很好就记录分享给大家啦~,也顺便当成个笔记方便回顾(逃~))
树状数组是用数据压缩的思想由二进制实现的数据结构。有单点修改+区间查询或区间修改+单点查询的作用。
首先我们来看看暴力的效率。q组询问,极端情况下n个数的修改,效率为 O(n q) 。n,q为500000时一定会炸。
由于电脑一种叫做补码的操作(由于电脑是二进制,它们存的相反数是它的取反+1),一个数与它的相反数做与操作时会返回二进制下最右边的1的位置。举个例子: 6&-6=2 将6变成二进制:110。其中最右侧的1加粗字体:110则返回的是二进制下10的值:2。得到代码:
- //ll就是long long
- ll lowbit(ll num){
- return num&-num;
- }
为了简化区间修改的效率,我们需要建立这样的一个数组:数组中第k位的值为原数组中的一段区间和,这个区间的长度是lowbit(k),终点是k。 比如:输入一个数组,那么我们所建立的数组:
我们就称这种数组为树状数组
代码
- void build(ll s,ll num){
- for(ll i=s;i<=n;i+=lowbit(i)) tree[i]+=num;//当s在i的范围内 第num位数组加上num
- }
跟着代码走一遍:
至此,建树操作已经完成了。可以发现这个操作的本质就是修改树状数组的值,所以它也是单点修改的函数。
- //反着的建树。此操作是求输入的数据第1到第s位的和。
- ll ask(ll s){
- ll ans=0;
- for(ll i=s;i>=1;i-=lowbit(i)) ans+=tree[i];//建树的反操作
- return ans;
- }
举一个sample(example) 求第三项和第五项之间的和。 本质上就是求5的前缀和与2的前缀和的差。(1 2 3 4 5)-(1 2)=(3 4 5)
检验一下,3到5位的求和就是4+2+3=9。没有问题!
ACcode:
-
- #include<bits/stdc++.h>
- using namespace std;
- #define int long long
- const int N=5e5+10;
- int n,m,q,in[N],tr[N];
- int lowbit(int num) {
- return num&-num;//返回值为二进制下num从左往右第一个1的位置
- }
- void add(int s,int num) {
- for(int i=s; i<=n; i+=lowbit(i))tr[i]+=num;
- //当s在i的范围内,第num位数组加上num
- }
- int ask(int s) {
- int ans=0;
- for(int i=s; i>=1; i-=lowbit(i))ans+=tr[i];//寻找差分的标记
- return ans;
- }
- void solve() {
- cin>>n>>q;
- for(int i=1; i<=n; i++) cin>>in[i];
- while(q--) {
- int op;
- cin>>op;
- if(op==1) {
- int x,y,s;
- cin>>x>>y>>s;
- add(x,s);//差分
- add(y+1,-s);
- } else {
- int x;
- cin>>x;
- cout<<in[x]+ask(x)<<"\n";//区域查询则为右边界前缀和减去左边界前缀和
- }
- }
- }
- signed main() {
- ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
- int t=1;
- // cin>>t;
- while(t--) {
- solve();
- }
- return 0;
- }
-
-
-
-
over~