树状数组浅讲

树状数组浅讲

rt,个人肤浅理解,各位神犇请自动出门右转

不同于传统数组每个元素单独存放,求和时遍历相加,树状数组每个元素不单独维护,而是被维护在一个包含其他元素的前缀和里。宜先仔细揣摩后再行。

上图便体现了树状数组储存数据的原理

相当于以下等式

说明: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];

如此存储,便可以体现出上面所说每个元素不单独维护,而是被维护在一个包含其他元素的前缀和里的维护原则,比如元素1(即\(A[1]\))便包含在C[1],C[2],C[4],C[8]中,而如今有一个神器lowbit(x)=x&(-x)可以依次推出所有包含元素i的\(C[k]\)们,我们先看看1至8的lowbit值

lowbit(1)=1

lowbit(2)=2

lowbit(3)=1

lowbit(4)=4

lowbit(5)=1

lowbit(6)=2

lowbit(7)=1

lowbit(8)=8

这时你会发现一个规律:每次当前下标再加上当前下标的lowbit值便会得到下一个包含元素i的\(C[k]\)的下标,比如第一个包含元素1的\(C[k]\)为\(C[1]\),那么下一个(第二个)包含元素1的\(C[k]\)为\(C[1+lowbit(1)]\),如此按照此规律循环便可访问到所有包含元素i的\(C[k]\)们

于是下面遍历所有包含元素i的\(C[k]\)的程序成立

void spot(int x)
{
    for(int i=x;i<=n;i+=lowbit(i))
        //do something
}

于是你又可以在遍历的同时顺便修改一下值(又于是下面修改元素i的值的程序成立)

void add(int x, int v)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tree[i]+=v;
}

同理,你又会发现其实查询前缀和的规律也是如此,读者可以动手模拟一下

于是查询前缀和的程序又成立

int getsum(int x)
{
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))
        ans+=tree[i];
    return ans;
}

完整的树状数组板子

int lowbit(int x){return x&(-x);}
void add(int x, int v)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tree[i]+=v;
}
int getsum(int x)
{
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))
        ans+=tree[i];
    return ans;
}

以上。

原文地址:https://www.cnblogs.com/santiego/p/9551655.html

时间: 2024-11-09 01:50:39

树状数组浅讲的相关文章

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

浅谈树状数组

还是区间求和区间修改的问题,我们使用线段树解决以后发现编程复杂度比较大 在这里介绍一个简单的数据结构,树状数组. 树状数组的优势是编程复杂度小,常数小,时间复杂度也不错 树状数组的查询,修改,都是LOG(N)级别的 下面来分析一下上面那个图看能得出什么规律: 据图可知:c1=a1,c2=a1+a2,c3=a3,c4=a1+a2+a3+a4,c5=a5,c6=a5+a6,c7=a7,c8=a1+a2+a3+a4+a5+a6+a7+a8,c9=a9,c10=a9+a10,c11=a11.......

浅谈树状数组套主席树

话说主席树还没写就先写这一篇了\(qwq\) 回顾一下主席树的实现过程:类似查分思想,将线段树的每次修改看做函数式以支持可持久化.因为这样的线段树是可减的. 那么我们维护信息的时候,就要维护每一次新形成的信息.但是我们可以根据前一个信息的基础上进行改动,而不必要去再建一棵树. 所以总而言之,是前缀和的思想. 那么,当需要修改的时候,怎么做呢? 考虑普通的区间操作,当做单点修改的时候,一般用树状数组,线段树和分块.最好实现的就是树状数组. 考虑用树状数组来维护主席树的信息. 树状数组中维护了每一次

POJ 2155 Matrix (树状数组 &amp;&amp; 区间计数)

题意 : 给出一个N*N的矩阵, 矩阵只有可能包含0或1, 一开始则全部是0.对于矩阵可以进行两种操作, 第一种是输入 C x1 y1 x2 y2 表示, 对以(x1, y1)为左上角, 以(x2, y2)为右下角构成的矩形区域内的数全部进行取反操作, 即0变1.1变0.第二种是Q X Y, 表示查询现在这个矩阵的(X, Y)点到底是0还是1.总共有T次操作, 对于C操作进行相应的修改, 对于Q操作要对应输出! 分析 : 据说是楼教主出的题, 自己确实想不出什么高效的办法, 参考网上的题解, 才

cf1208 D Restore Permutation (二分+树状数组)

题意 让你构造一个长度为n的序列,记为p1……pn,(这个序列是1~n的全排列的一种) 给你n个数,记为s1……sn,si的值为p1……pi-1中小于pi的数的和. 思路 显然,应该倒着来,也就是从pn 开始构造,这样的话,当要填pi 的时候,p1到pi-1就是所有的还未填的数,那么我们只需要去找哪个前缀和符合就可以了. 比如:现在还没填进去的数是 1,2,3,4,5 而我现在的si 是6,那么,对应的pi 就是4,因为这样无论1,2,3,5怎么排,都符合si 为6 (才学疏浅,不怎么会讲) 那

hdoj 1556 Color the ball(树状数组)

Problem Description N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的"小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色.但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗? Input 每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <=

HDU1166 敌兵布阵(树状数组)

敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 60510    Accepted Submission(s): 25649 Problem Description C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务

BZOJ1176---[Balkan2007]Mokia (CDQ分治 + 树状数组)

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1176 CDQ第一题,warush了好久.. CDQ分治推荐论文: 1 <从<Cash>谈一类分治算法的应用> 陈丹琦 2 <浅谈数据结构题的几个非经典解法>  许昊然 关于CDQ分治,两种要求:①操作不相互影响  ②可以离线处理 题目描述是有问题的,,初始时 全部为0,不是s 题意:二维平面内,两种操作,1 x y v ,位于(x,y)的值加上v...2 x1,

BZOJ3295 动态逆序对 树套树, 树状数组套线段树(主席树)

Orz黄学长,蒟蒻在黄学长的带领下,通过阅读黄学长的代码!终于会了这道题! 首先我想先说一下这道题的思路(准确来说是黄学长的). 很明显,树状数组应该不用讲吧!关键是内存怎么开,维护一些什么样的数据? 其实我们通过观察,很快可以发现,你维护被删的数比维护所有的数轻松多了(不管是空间上,还是时间上).所以我们就可以从这方面想!(其实我一开始的思路,因为这道题我已经看过很久了,一直想写,毕竟是白书里面的一道例题嘛!一开始,蒟蒻的我是打算这样的用树状数组套权值线段树,并且是维护所有的数,我发现空间不够