草庐IT

树状数组

Aska 2023-03-28 原文

简述

什么是树状数组呢,顾名思义就是树一样的数组,本质就是用数组模拟树形结构。  

树状数组有什么用呢,树状数组可以实现单点更新,单点查询,区间查询和区间更新,维护的东西和线段树可以类比的,就是满足区间加法性质的属性,例如最值,和,gcd等。

树状数组可以干的东西线段树也能干,但线段树干的东西树状数组不一定能干。树状数组的复杂度和线段树同级,但常数更低且代码量更为简答,所以我们能用树状数组就用树状数组,这样就不容易TLE了。

lowbit操作

首先我们来了解一个操作,lowbit(x),这个操作取出x二进制最低位的1,例:lowbit(10100)=100

ll lowbit(ll x){
    return x&(-x);
}

为什么是x&(-x)呢?我们知道对一个数取负就是对这个数取反加一。我们设原数的二进制为xxx1000...,该数取反为yyy0111...,令其加一,我们可以发现,取反加一后的数只有一位是1,且该位置就是原数最低位的1,所以我们令x&(-x)就可以得到x的lowbit了。

树的样子

对于一般的二叉树,形状是这样的 

  此时我们令其偏一下身子,使其右倾变为这样

 如果每个节点都存东西,那就不叫树状数组叫线段树了,那他们有什么不同呢?我们来看下图:  

底下的代表我们输入的a数组,上面的代表我们的树状数组c数组。从空间上来说,树状数组只需要存二叉树的根和左子树就行,因为右子树可以由根减左子树获得,则有:

•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[i]存了lowbit(i)个的和。

单点更新,区间求和

单点更新

例如我们需要令a[1]+=k,则需要维护c1,c2,c4,c8这些包含a1的节点,我们可以发现这些需要维护的节点是层次关系:c1<c2<c4<c8,也就是说我们只要更新本节点以及本节点的所有父亲就可以实现更新操作,而本结点兄弟维护的叶子数和本节点维护的相同,还记得之前我们观察的规律C[i]存了从i开始往前数共lowbit(i)个位置的和吗,我们只需要加上lowbit(本节点的下标),即可得到本节点的父亲,一直递归到根,就完成了更新。

区间求和

例如我们需要查询区间3到5的和,其值等于a3+a4+a5,如果我们得到前缀和sum[i],其值就等于sum[5]-sum[2],而树状数组恰恰就是利用了前缀和求出区间和。  

例如我们要求sum(7),那么sum(7)=c[7]+c[6]+c[4],我们可以很容易看出,6是由7-lowbit(7)得到的,4是由6-lowbit(6)得到的,而4减它的lowbit就等于0了。我们可以知道,sum(i)可由每个c位置的贡献求出,每个位置为上个位置减lowbit。为什么可以这样呢,我们来看i=2的幂时,这时c[i]就完全包含1到i这i个元素,无需做任何处理,当i不是2的幂时,需要一个一个处理,直到i消掉低位的1变为2的幂,例如i=7的时候,一开始lowbit(7)=1,也就是说c[7]只维护了一个节点那就是a[7],令其减loewbit,得i=6,此时c6维护了a5和a6两个节点,减去lowbit得i=4,包含1到i所有节点,故加上贡献后结束。  

所以下一个有贡献的位置是当前位减lowbit(当前)。

构建

此时c数组就是这个树状数组了,c[x]保存的是从下标x开始 a[x]+a[x-1]+a[x-2]+a[x-k] ,至于有几个a[]相加,这里有一个计算方法,把x化为二进制,从右向左找到第一个1的位置,这个位置所代表的十进制的数字k就意味着c[x]=a[x]+a[x-1]+a[x-2]+...+a[x-k-1];

void build(){
    for (int i=1; i<=n; i++)
        for (int j=i; j>=i-lowbit(i)+1; j--)
            c[i]+=a[j];
}
void update(int x,int val,int n){
    //x位置的值加上val,n为总叶子个数 
    for(int i=x;i<=n;i+=lowbit(i)){
        c[i]+=val;
    } 
}
ll sum(int x){
	ll ans1=0;
	for(int i=x;i>=1;i-=lowbit(i)){
		ans1+=c[i];
	}
	return ans1;
}
ll sum(int x,int y){//区间查询
	return sum(y)-sum(x-1);
}

区间更新,单点求值

如果单纯的对原数组的树状数组进行区间求和,不难看出这是一件复杂度爆炸的事情;所以我们不妨对原数组求其差分数组,然后对差分数组建立树状数组,那么区间修改就变为了对差分数组的\(左右端点的两次单点修改\),是不是就快多了呢?

同样,单点查询就可以看作是对差分数组的区间求和,这样又转换成了最基本的树状数组操作问题。

void build(){
for(int i=1;i<=n;i++){
	cin>>a[i];
	b[i]=a[i]-a[i-1];  //b是差分数组,求b的树状数组可以将区间修改变为两次单点修改,将单点查询改为一次区间求和 
          c[i]=b[i];
          for(int j=i-lowbit(i)+1;j<=i-1;j++){  //构建树状数组 
		   c[i]+=b[j];
		}
	}
}
void update(int x,int y,int k){//区间[x,y]加上k
  for(int j=x;j<=n;j+=lowbit(j)){  
		c[j]+=k;
  }
  for(int j=y+1;j<=n;j+=lowbit(j)){  //两次单点修改 
		c[j]-=k;
  }

}
int  get(int x){//单点求值
int ans=0;
  for(int j=x;j>0;j-=lowbit(j)){ //区间求和,将区间拆分成多个区间的和 
		ans+=tree[j];
}
return ans;
}

模版

#include<iostream>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;
ll lowbit(ll x){
    return x&(-x);
}
void update(int x,int val,int n){
    
    for(int i=x;i<=n;i+=lowbit(i)){
        c[i]+=val;
    } 
}
ll sum(int x){
	ll ans1=0;
	for(int i=x;i>=1;i-=lowbit(i)){
		ans1+=c[i];
	}
	return ans1;
}
ll sum(int x,int y){
	return sum(y)-sum(x-1);
}
void build(){
    for (int i=1; i<=n; i++)
        for (int j=i; j>=i-lowbit(i)+1; j--)
            c[i]+=a[j];
}
int main()
{
    
    return 0;
}

7.28已更新

有关树状数组的更多相关文章

  1. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  2. ruby - 多次弹出/移动 ruby​​ 数组 - 2

    我的代码目前看起来像这样numbers=[1,2,3,4,5]defpop_threepop=[]3.times{pop有没有办法在一行中完成pop_three方法中的内容?我基本上想做类似numbers.slice(0,3)的事情,但要删除切片中的数组项。嗯...嗯,我想我刚刚意识到我可以试试slice! 最佳答案 是numbers.pop(3)或者numbers.shift(3)如果你想要另一边。 关于ruby-多次弹出/移动ruby​​数组,我们在StackOverflow上找到一

  3. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  4. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  5. ruby - 检查数组是否在增加 - 2

    这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife

  6. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  7. ruby - 在 Ruby 中用键盘诅咒数组浏览 - 2

    我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作

  8. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  9. ruby - 使用多个数组创建计数 - 2

    我正在尝试按0-9和a-z的顺序创建数字和字母列表。我有一组值value_array=['0','1','2','3','4','5','6','7','8','9','a','b','光盘','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','','u','v','w','x','y','z']和一个组合列表的数组,按顺序,这些数字可以产生x个字符,比方说三个list_array=[]和一个当前字母和数字组合的数组(在将它插入列表数组之前我会把它变成一个字符串,]current_combo['0','0','0']

  10. ruby - 在哈希的键数组中追加元素 - 2

    查看我的Ruby代码:h=Hash.new([])h[0]=:word1h[1]=h[1]输出是:Hash={0=>:word1,1=>[:word2,:word3],2=>[:word2,:word3]}我希望有Hash={0=>:word1,1=>[:word2],2=>[:word3]}为什么要附加第二个哈希元素(数组)?如何将新数组元素附加到第三个哈希元素? 最佳答案 如果您提供单个值作为Hash.new的参数(例如Hash.new([]),完全相同的对象将用作每个缺失键的默认值。这就是您所拥有的,那是你不想要的。您可以改用

随机推荐