树状数组学习小结

龙头复盘神器/复盘于导出工具最新版5.6 龙头复盘神器,复盘于导出工具 阅读详情

树状数组,又称二进制索引树,英文名Binary Indexed Tree。

一、树状数组的用途

主要用来求解数列的前缀和,a[0]+a[1]+...+a[n]。

由此引申出三类比较常见问题:

1、单点更新,区间求值。(HDU1166)

2、区间更新,单点求值。(HDU1556)

3、求逆序对。(HDU2838)

 

二、树状数组的表示

1、公式表示

设A[]为一个已知的数列。C[]为树状数组。则会有

C[i]=A[j]+...+A[i];j=i&(-i)=i&(i^(i-1))。

2、图形表示

(注:1、最下面的一行表示数组A,上面的二进制表示的部分是C;

2、图片来源于http://hi.baidu.com/rain_bow_joy/blog/item/569ec380c39730d2bc3e1eae.html)

 

从以上可以发现:

1、树状数组C是表示普通数组A的一部分的和。

2、小标为奇数时,C[i]只能管辖一个A[i]。

3、C[i]的最后一个数一定是A[i]。

 

三、树状数组的关键代码

1、

int lowBit(int x)
{
    return x&(-x);
}

这段代码可以简单的理解为是树状数组向前或向后衍生是用的。

向后主要是为了找到目前节点的父节点,比如要将C[4]+1,那么4+(4&(-4))=8,C[8]+1,8+(8&(-8))=16,

C[16]+1。

向前主要是为了求前缀和,比如要求A[1]+...+A[12]。那么,C[12]=A[9]+...+A[12];然后12-12&(-12)=8,

C[8]=A[1]+...+A[8]。

 

2、

void modify(int pos,int num)  //pos为数组下标位置,num为要增加的值 
{
    while(pos<=n)   //n为数组的长度 
    {
        c[pos]+=num;
        pos+=lowBit(pos);
    }
}

这段代码是用来更新树状数组的,包括区间更新、单点更新。

就是想刚才所说的,一点更新了,要不断将父节点也更新。

 

3、

int getResult(int pos)  //求A[1]+...+A[pos] 
{
    int sum=0; 
    while(pos>0)
    {
        sum+=c[pos];
        pos-=lowBit(pos);
    }
    
    return sum; 
}                

这段代码用来求解前缀和的。

就像刚才说的,求解A[1]+...+A[12],也就是C[12]+C[8]搞定。

 

四、树状数组的优点

1、原本的长度为n的数列求和时间复杂度为O(n),更改的时间复杂度为O(1)。

树状数组将其优化为O(logn)。在n较大时,效率更高。

2、树状数组编码简单。

 

五、注意

1、树状数组的下标要从1开始。

2、在学习的过程中遇到这么个问题。不知道为什么pos+pos&(-pos)就到了pos的父节点,也不知道

为什么pos-pos&(-pos)就得到了下一个无联系的节点,从而可以得到前缀和。

我只能说:我不懂如何证明,这是数学问题了,树状数组的发明者应该就是发现了这点才搞出树状

数组的吧。初学者不妨抛开这点,专注于事实,将上面的图形自己计算画一遍,非常有利于理解。

 

六、符代码:

HDU1166

单点更新,区间求值

#include<iostream>
using namespace std;

const int maxn=50001;

int a[maxn];
int c[maxn];
int n; 

int lowBit(int t)
{
    return t&(-t);
}

void modify(int t,int num)
{
    while(t<=n)
    {
        c[t]+=num;
        t+=lowBit(t);
    }
}

int getResult(int t)
{
    int num=0; 
    while(t>0)
    {
        num+=c[t];
        t-=lowBit(t);
    }
    
    return num; 
}

void init()
{
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        
        modify(i,a[i]); 
    }
}


int main()
{
    int cas,Case=1;
    
    scanf("%d",&cas); 
    while(cas--)
    { 
        memset(c,0,sizeof(c)); 
        printf("Case %d:\n",Case++);
         
        scanf("%d",&n);
        
        init();
        
        char ch[15];
        int a,b;   
        while(scanf("%s",&ch),strcmp(ch,"End"))
        { 
            scanf("%d%d",&a,&b);
             
            switch(ch[0])
            {
                case 'Q':
                    printf("%d\n",getResult(b)-getResult(a-1));
                    break; 
                case 'A': 
                    modify(a,b);
                    break;
                case 'S':
                    modify(a,-b);
                    break;
            }
         }
     }
     
     system("pause");
     return 0;
} 

 

HDU1556

区间更新,单点求值

#include<iostream>
#include<cstring>
using namespace std;

const int maxn=100001;

int c[maxn];
int n;

int lowbit(int t)
{
    return t&(-t);
}

void insert(int t,int d)
{
    while(t<=n)
    {
        c[t]+=d;
        t+=lowbit(t);
    }
}

int getSum(int t)
{
    int sum=0;
    while(t>0)
    {
        sum+=c[t];
        t-=lowbit(t);
    }
    
    return sum;
}

int main()
{
    while(cin>>n,n)
    {
        int a,b;
        memset(c,0,sizeof(c));
        
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&a,&b);
            
            insert(a,1);
            insert(b+1,-1);
        }
        
       for(int j=1;j<n;j++)
       {
            printf("%d ",getSum(j));
       }
       printf("%d\n",getSum(n));
    }
    
    system("pause");
    return 0;
}

 

HDU2838

求逆序对

#include<iostream>
#include<cstring>
using namespace std;

const int maxn=100001;
 
struct node
{
    int cnt;
    __int64 sum;
}tree[maxn];         
        
int n;

int lowBit(int x)
{
    return x&(-x);
}

void modify(int x,int y,int t)
{
    while(x<=n)
    {
        tree[x].sum+=y;
        tree[x].cnt+=t;  //tree[].cnt来保存是否出现过a 
        x+=lowBit(x);
    }
}

__int64 query_cnt(int x)   //比x小的数的个数 
{
    __int64 sum=0;
    while(x>0)
    {
        sum+=tree[x].cnt;
        x-=lowBit(x);
    }
    
    return sum;
}

__int64 query_sum(int x)  //比x小的所有数之和 
{
    __int64 sum=0;
    while(x>0)
    {
        sum+=tree[x].sum;
        x-=lowBit(x);
    }
    
    return sum;
}

int main()
{
    while(~scanf("%d",&n))
    {
        int a;
        __int64 ans=0; 
        memset(tree,0,sizeof(tree));
         
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a);
            
            modify(a,a,1);  //以a为下标更新数组 
            
            __int64 k1=i-query_cnt(a);   //k1为前i个数比a大的数的个数 
            if(k1!=0)
            {
                __int64 k2=query_sum(n)-query_sum(a); //目前所有数的和-目前所有比a小的数的和,为比a大的数的和   
                ans+=k1*a+k2;   //调换a所需的时间 
            }
        }
        
        printf("%I64d\n",ans); 
    }
    
    system("pause");
    return 0;
} 

 

七、二维树状数组

C[x][y]=sum(A[i][j])。其中,x-lowBit(x)+1<=i<=x,y-lowBit(y)+1<=j<=y。

例题:HDU1892

二维树状数组一般就是对矩阵的操作,更新、求值。。。

代码:

#include<iostream>
#include<cstring>
using namespace std;

const int maxn=1005;

int c[maxn][maxn];

int lowBit(int x)
{
    return x&(-x);
}

void modify(int x,int y,int val)
{
    for(int i=x;i<maxn;i+=lowBit(i))
    {
        for(int j=y;j<maxn;j+=lowBit(j))
        {
            c[i][j]+=val;
        }
    }
}

int getResult(int x,int y)
{
    int sum=0;
    for(int i=x;i>0;i-=lowBit(i))
    {
        for(int j=y;j>0;j-=lowBit(j))
        {
            sum+=c[i][j];
        }
    }
    
    return sum;
}

int getVal(int x,int y)
{
    return getResult(x,y)-getResult(x-1,y)-getResult(x,y-1)+getResult(x-1,y-1);
}

void init()
{
    memset(c,0,sizeof(c));
    
    for(int i=1;i<maxn;i++)
    {
        for(int j=1;j<maxn;j++)
        {
            modify(i,j,1);
        }
    }
}
    
int main()
{
    int cas,cas1=1,query;
    
    scanf("%d",&cas);
    while(cas--)
    {
        init();
        
        scanf("%d",&query);

        printf("Case %d:\n",cas1++);
        for(int i=1;i<=query;i++)
        {
            char ch;
            int x1,y1,x2,y2,n;
            
            getchar();
            scanf("%c",&ch);
            
            switch(ch)
            {
                case 'S':
                    {
                    scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
                    int x11=min(x1,x2);
                    int x22=max(x1,x2);
                    int y11=min(y1,y2);
                    int y22=max(y1,y2);
                    printf("%d\n",getResult(x22+1,y22+1)-getResult(x11,y22+1)-getResult(x22+1,y11)+getResult(x11,y11));
                    break;
                    }
                case 'A':
                    {
                    scanf("%d%d%d",&x1,&y1,&n);
                    modify(x1+1,y1+1,n);
                    break;
                    }
                case 'M':
                    {
                    scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&n);
                    int v=getVal(x1+1,y1+1);
                    int Min=min(n,v);
                    modify(x1+1,y1+1,-Min);
                    modify(x2+1,y2+1,Min);
                    break;
                    }
                case 'D':
                    {
                    scanf("%d%d%d",&x1,&y1,&n);
                    int v=getVal(x1+1,y1+1);
                    int Min=min(v,n);
                    modify(x1+1,y1+1,-Min);
                    break;
                    }
            }
        }
    }
    
    system("pause");
    return 0;
}


 

八、参考文章
http://mindlee.net/2011/07/10/binary-indexed-trees/ 

http://dongxicheng.org/structure/binary_indexed_tree/

五.EtherCAT开发之STM32F407ZGt6+ LAN9253 外置EEPROM配置数据含义 EtherCAT从站简称ESC,ESC在上电过程中,会自动加载EEPROM中前7个字(1word = 2 byte)到ESC寄存器。ESC寄存器总是可以被EtherCAT主站通过EtherCAT总线访问,无论PDI接口寄存器配置什么值。EEPROM存储器中的前7个字,将决定ESC的工作模式。 阅读详情

相关推荐

什么是Merkle树?用“文件柜”类比理解区块链的核心技术

Merkle树是一种用于区块链的数据结构,像一个高效的“文件柜”,用于存储和验证大量交易数据。比特币通过Merkle树将交易哈希组织成树状结构,层层汇总,最终生成一个唯一的根哈希,存储在区块头中。这种设计大幅减少数据存储和验证成本,支持快速校验交易的完整性和存在性。Merkle树通过密码学保证数据不可篡改,广泛应用于比特币、区块链及其他分布式系统。它的优势在于高效、轻量和安全,尤其适合轻节点验证和数据同步。理解Merkle树就像掌握区块链的“索引秘籍”,是Web3世界的关键技术。

在路上的专栏 1193

c语言void modify,void modify_emp修改记录的c语言流程图

满意答案vflyK2016.01.14采纳率:57%等级:6已帮助:156人#include #include void stat(char* s,int* num){char* from_1 = s;char* from_2 = s;int strLen = 0;int nLen = 0;while(*from_1) //算字符串长度{strLen++;from_1++;}while(*...

weixin_29847829的博客 607

Obsidian从零搭建:可验证、可回溯、可演进的知识库工具链

个人知识库不是开箱即用的软件,而是基于Markdown内核构建的可演进系统。其核心原理在于分离‘稳定内核’与‘可插拔扩展’:Obsidian原生支持双链、嵌入、Frontmatter等基础能力,构成不可降级的知识骨架;而Dataview、Templater等插件则作为可验证、可回溯的增强层,需通过三问验证(解决痛点?有原生替代?失效可降级?)方可引入。这种设计赋予知识库技术价值——抗版本升级、跨平台兼容、离线可用,并支撑技术文档管理、项目复盘、读书笔记等真实场景。当面对computer use插件不可用或h

weixin_30660027的博客 684

树状数组(详细分析+应用),看不懂打死我!

树状数组介绍 在学习一个算法之前一定要清楚它能干嘛,能解决什么样的问题,对你解题是否有帮助,然后才去学习它! 那么接下来看如下几个问题 什么是树状数组 顾名思义就是一个结构为树形结构的数组,于二叉树的结构类似但又不同,它是在二叉树的结构上删除了一些中间节点,来看两幅图就明白了. 1.这是二叉树的结构 2.这是树状数组的结构 不难发现,树状数组相比于二叉树删除了一些节点,但是为什么要删除呢?这就和树状数组的一些性质(lowbit)有关了,不懂没关系,继续往下看. 树状数组可以解决什么问题呢? 可以解决大部

TheWayForDream 8万+

Nordic开发者必备:SEGGER Embedded Studio免费许可证申请与配置全攻略

本文为Nordic芯片开发者提供了SEGGER Embedded Studio(SES)集成开发环境的详细配置指南。内容涵盖免费许可证的合规申请流程、项目创建、Nordic SDK集成,以及编辑器优化、构建调试配置、RTT日志输出等核心功能的高效设置,旨在帮助开发者构建深度优化的专业开发环境,显著提升嵌入式开发效率。

cheese的博客 353

MoveIt2 Servo实战:解锁UR机械臂在ROS2中的实时遥操作与动态响应

本文深入探讨了MoveIt2 Servo在UR机械臂实时遥操作中的应用,展示了如何通过ROS2实现毫秒级动态响应。文章详细解析了Servo的核心价值、环境配置、控制器适配及高级调优技巧,特别适用于需要高精度控制的工业场景,如VR手术训练和汽车装配线。

weixin_29044157的博客 249

【高级数据结构】树状数组

树状数组(BinaryIndexedTree,BIT)(Binary Indexed Tree,BIT)(BinaryIndexedTree,BIT)是利用数的二进制特征进行检索的一种树状的结构。如何利用二分的思想高效地求前缀和? 如图4.74.74.7所示, 以AAA =[a1,=[a_1,=[a1​,a2a_2a2​ ,a3a_3a3​…a8]a_8]a8​]为例,将二叉树的结构画成树状。这幅图是树状数组的核心,理解了它,就能明白树状数组的一切操作。图4.74.74.7圆圈中标记有数字的节点,存储的是称

Antonio915的博客 6178

FPGA中参数化SPI接口设计与Verilog实现

SPI(串行外设接口)作为嵌入式系统的核心通信协议,通过全双工、高速率特性在FPGA与Flash、ADC等外设交互中占据重要地位。参数化设计通过抽象时钟极性、数据位宽等可配置维度,采用分层架构实现硬件复用,显著提升开发效率。Verilog实现需重点处理跨时钟域同步和可配置帧结构,结合AXI总线集成与UVM验证方法学,可构建适应工业控制、物联网网关等场景的高可靠SPI解决方案。

ciya3282的博客 1408

实用技巧——数状数组

1.划分区间 区间结尾是R,区间长度等于R的 二进制分解 下最小的2的次幂,即lowbit® 7->[7,7],[6,5],[4,1](分的过程就是求出7->6->4->0) lowbit实现 while(x>0){ cout<<x-(x&(-x))+1<<" "<<x<<endl; x-=x&(-x); } ...

Albert_Jw的博客 711

跟着Cell学单细胞转录组分析(五):单细胞转录组marker基因鉴定及细胞群注释

书接上回(跟着Cell学单细胞转录组分析(四):单细胞转录组测序UMAP降维聚类)。完成数据降维和细胞聚类后,最主要的环节和工作就是确定各个细胞群,明确是什么类型的细胞,正群的细胞定群很关键,涉及到整个研究,所以这一步宁愿多费时间,也不要出错。当然,这也不是一蹴而就的,需要反复的确认。 要确定各个群是什么细胞,首先需要了解细胞群的marker基因,因为不同类型的细胞突出 表达的基因也是不同的。这里使用FindAllMarkers鉴定各个细胞群的高表达基因。 DefaultAssay(scedata)

qq_42090739的博客 1万+

WPS访问权限不足怎么解决??具体怎么操作?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值。

**My Coding Family** 4172

树状数组(超详细)

树状数组千字精华讲解

Jiguoyuanyic的博客 4229

基于Simulink的发动机管理系统中的自适应增压控制系统设计

通过上述详细设计步骤,我们成功开发了一个基于Simulink的自适应增压控制系统。该系统不仅能够实时监测和预测发动机的运行状态,动态调整增压压力目标值,还具备实时监控、故障诊断和保护功能,确保系统的安全性和可靠性。此外,通过仿真实验和硬件在环测试,我们验证了系统的性能,并对其进行了优化,使其能够更好地满足实际应用需求。这个项目展示了如何利用MATLAB/Simulink的强大功能,结合控制理论和工程实践,开发出高效、可靠的自适应增压控制系统。

MHD0815的博客 221

树状数组的学习小结

树状数组,又称二进制索引树,英文名Binary Indexed Tree。 一、树状数组的用途 主要用来求解数列的前缀和,a[0]+a[1]+...+a[n]。 由此引申出三类比较常见问题: 1、单点更新,区间求值。(HDU1166) 2、区间更新,单点求值。(HDU1556) 3、求逆序对。(HDU2838)   二、树状数组的表示 1、公式

lanshui_Yang的专栏 1188

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

树状数组(学习小结)

树状数组 树状数组是一种数据结构,它可以高效处理对一个存储数字的列表进行更新和维护前缀和 已知一个长度为n的数组,现在要实现以下操作: 更新某个位置的值,查询前i个数的和,查询区间[i,j]的和 upData(i, val): 在i位置加上val query(i):查询区间从第一个数到第i个数的和 getSum(i, j): 计算第i个数到第j个数的和 树状数组和线段树 树状数组可线段树...

Tw_light的博客 325

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

树状数组学习小结(很久以前的了。。。)

PS:很久以前的,翻东西的时候看到,就一起放上来吧。。。。 树状数组总结 最近学了一个新的数据结构:树状数组,那今天就来写篇总结吧。 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构,我们通常都叫它BIT。        挑程(《挑战程序设计》)上面说了,BIT是删去了线段树多余的区间从而组成的一棵...

SHIP&GOAT的博客 293

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

技能树new——树状数组学习教材

搞懂树状数组 树状数组详解 树状数组小结 树状数组应用

最弱勇者哀嚎地 1036

树状数组小结

文章目录前言一、树状数组的作用二、二进制拆分三、lowbit的使用四、基本使用1.单点修改(前缀和)2.区间查询(前缀和)3.区间修改(差分数组)4.单点查询(差分数组)5.区间查询+区间修改五、进阶使用1.求逆序对数2.区间求最值3.查询第K大/小的数4.二维树状数组 前言 对于一个数组,我们常见的操作为修改和查询,那么对于一个长度为N的数组来说,我们查询和修改一个点的时间复杂度为O(1),而对于修改并查询一个区间的复杂度则为O(N),如果对于区间的操作远多于对单点的操作,那么我们就引入了差分数组与前

qq_52441682的博客 848

树状数组小结-二维

树状数组: 模板: 取数组下标二进制非0最低位所表示的值; 区间查询。 单点更新: 同一维树状数组比较,这样比较好理解; 如果一维不会可以看看我的一维树状数组博客 1.一维二维都是:取数组下标二进制非0最低位所表示的值; int lowbit(int x) { return x&(-x); } 2. 一维树状数组更新是这样的: void add(int x,int val) { ...

不想起标题 278

树状数组小结及其注意事项

树状数组中用的d【】,每个点都有一定的管辖范围; 如d[1]=a[1]; d[2]=a[1]+a[2]; d[3]=a[3]; d[4]=a[1]+a[2]+a[3]+a[4]; 等等; 这样的结构关键是为了,对一个数组内部动态的删除,增加,来高效的求某个点或者某个区间的值; 比如说对数组a,改变某一位的值需O(1),求某个k区间值O(k); 这样的

a_ppler的专栏 559
上一篇: 有向图强连通分量之tarjan算法
下一篇: 1/8/2012 ICPC培训 第十七天
lulipeng_cpp
博客等级 码龄15年 169粉丝 130原创
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值