首页 > 其他分享 >高效维护区间之和/区间最值的数据结构(一)——树状数组

高效维护区间之和/区间最值的数据结构(一)——树状数组

时间:2024-07-08 19:54:59浏览次数:21  
标签:树状 int lowbit 查询 num 数组 区间 最值

高效维护区间之和/区间最值的数据结构(一)——树状数组

树状数组的核心思想:分治。将数组以二叉树的形式进行维护区间之和。

设 a a a为原数组, t r e e tree tree为树状数组。 t r e e tree tree数组用于存储树上该结点下严格直连的子节点之和(例: t [ 1 ] = a [ 1 ] , t [ 2 ] = t [ 1 ] + a [ 2 ] , t [ 3 ] = a [ 3 ] , t [ 4 ] = t [ 2 ] + t [ 3 ] + a [ 4 ] t[1]=a[1],t[2]=t[1]+a[2],t[3]=a[3],t[4]=t[2]+t[3]+a[4] t[1]=a[1],t[2]=t[1]+a[2],t[3]=a[3],t[4]=t[2]+t[3]+a[4], t [ 5 ] = a [ 5 ] , t [ 6 ] = t [ 5 ] + a [ 6 ] , t [ 7 ] = a [ 7 ] , t [ 8 ] = t [ 4 ] + t [ 6 ] + t [ 7 ] + a [ 8 ] , t[5]=a[5],t[6]=t[5]+a[6],t[7]=a[7],t[8]=t[4]+t[6]+t[7]+a[8], t[5]=a[5],t[6]=t[5]+a[6],t[7]=a[7],t[8]=t[4]+t[6]+t[7]+a[8],…),即存储 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [x−lowbit(x)+1,x]区间之和

树状数组的操作:向下查询 向上修改

向下查询:查询前一个能代表其和的元素(例: s u m ( 4 ) = t [ 4 ] , s u m ( 3 ) = t [ 3 ] + t [ 2 ] , s u m ( 2 ) = t [ 2 ] , … sum(4)=t[4],sum(3)=t[3]+t[2],sum(2)=t[2],… sum(4)=t[4],sum(3)=t[3]+t[2],sum(2)=t[2],…),可发现与下标的二进制表示有关,每次下标更新都在去掉二进制位中的最低的1,这一操作可用 i n d e x − = l o w b i t index-=lowbit index−=lowbit实现。

向上修改:向后更新到其所有的代表结点(例:修改 a [ 3 ] a[3] a[3],需要修改 t [ 3 ] 、 t [ 4 ] 、 t [ 8 ] t[3]、t[4]、t[8] t[3]、t[4]、t[8]),可以发现每次更新是在下标二进制中最后一个1上+1,因此可通过 i n d e x + = l o w b i t index+=lowbit index+=lowbit实现。

注意:0没有 l o w b i t lowbit lowbit,因此树状数组下标必须从1开始。

树状数组

lowbit函数的实现

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

修改函数的实现(向上修改)

void update(int i,int num){//向上修改
    for(;i<=N;i+=lowbit(i))
        tree[i]+=num;
}

查询函数的实现(向下查询)

int query(int i){//向下查询
    int ans=0;
    for(;i>=1;i-=lowbit(i))//注意tree数组下标从1开始
        ans+=tree[i];
    return ans;
}

单点修改 区间查询(前缀和版树状数组)

  • 初始化
void init(){//初始化tree数组
    for(int i=1;i<=n;i++)//注意tree数组下标从1开始
        update(i,a[i]);//在初始化tree数组时num所传入参数为原数组值 
}
  • 单点修改(以将 x x x加 n u m num num为例)
extern int x,num;
update(x,num);
  • 区间查询(以查询 [ l , r ] [l,r] [l,r]为例)
extern int l,r;
query(r)-query(l-1);

区间修改 单点查询(差分版树状数组)

  • 初始化
void init(){
    for(int i=1;i<=n;i++)
        update(i,a[i]-a[i-1]);//num传入差分数组
}
  • 区间修改(以 [ l , r ] [l,r] [l,r]均加 n u m num num为例)
extern int l,r,num;
update(l,num),update(r+1,-num);
  • 单点查询(以查询 x x x为例)
extern int x;
query(x);

区间修改+区间查询(二阶树状数组)

(注:此类问题也可采用线段树求解。)

a 1 + a 2 + . . . + a n = d 1 + ( d 1 + d 2 ) + . . . + ( d 1 + . . . + d n ) = n × d 1 + ( n − 1 ) × d 2 + . . . + d n a_1+a_2+...+a_n=d_1+(d_1+d_2)+...+(d_1+...+d_n)=n\times d_1+(n-1)\times d_2+...+d_n a1​+a2​+...+an​=d1​+(d1​+d2​)+...+(d1​+...+dn​)=n×d1​+(n−1)×d2​+...+dn​

= n ∑ i = 1 n d i − ∑ i = 1 n ( i − 1 ) d i =n\sum\limits_{i=1}^{n} d_i-\sum\limits_{i=1}^{n}(i-1)d_i =ni=1∑n​di​−i=1∑n​(i−1)di​(核心公式)

因此,维护两个树状数组,一个用于实现 d i d_i di​,另一个实现 ( i − 1 ) d i (i-1)d_i (i−1)di​

  • 查询函数、修改函数变更为:
void update1(int i,int num){
    for(;i<=n;i+=lowbit(i))
        t1[i]+=num;
}
void update2(int i,int num){
    for(;i<=n;i+=lowbit(i))
        t2[i]+=num;
}
int query1(int i){
    int ans=0;
    for(;i>=1;i-=lowbit(i))
        ans+=t1[i];
    return ans;
}
int query2(int i){
    int ans=0;
    for(;i>=1;i-=lowbit(i))
        ans+=t2[i];
    return ans;
}
  • 初始化函数(按照推导公式)
void init(){
    for(int i=1;i<=n;i++){
        update1(i,a[i]-a[i-1]);//对tree1传入差分数组
        update2(i,(i-1)*(a[i]-a[i-1]));//对tree2传入(i-1)*差分数组
    }
}
  • 区间修改(以 [ l , r ] [l,r] [l,r]均加 n u m num num为例)
extern int l,r,num;
update1(l,num),update1(r+1,-num);//对tree1加上差值
update2(l,(l-1)*num),update2(r+1,(r+1-1)*(-num));//对tree2加上差值(根据推导公式)
  • 区间查询(以查询 [ l , r ] [l,r] [l,r]为例)(本质:在求前缀和)
extern int l,r;
(r*query1(r)-query2(r))-((l-1)*query1(l-1)-query2(l-1));

标签:树状,int,lowbit,查询,num,数组,区间,最值
From: https://blog.csdn.net/Yerosius1/article/details/140277475

相关文章

  • Studying-代码随想录训练营day31| 56.合并区间、738.单调递增的数字、968.监控二叉树
    第31天,贪心最后一节(ง•_•)ง......
  • 区间DP专栏 第一章(双色马、神医胡青牛、Deque等)
    #A.神医胡青牛题目描述胡青牛是“倚天屠龙记”中的神医(但从此题目看出很贪财),每天都有N多(N<=2000)的人来求他治病,这些人排成一队,从1开始编号直到N,每个人手里都拿着一个牌子,其上的值用Ai(1<=i<=N,1<=ai<=1000)代表,表示自己愿意付给胡大牛多少钱做为酬金。胡神医每次从队......
  • 树状数组实现 查找逆序对
     题意:输入一个整数n。接下来输入一行n个整数 。1<=  <=n ,且每个数字只会出现一次题解:按每个数字的大小存入树状数组#include<bits/stdc++.h>usingnamespacestd;#definelllonglongconstintN=10000;intarr[N];lla[N];intn;llquery(intx){ll......
  • Studying-代码随想录训练营day30| 452.用最少数量的箭引爆气球、435.无重叠区间、763.
    第30天,贪心part04,加油,编程语言:C++目录452.用最少数量的箭引爆气球435.无重叠区间 763.划分字母区间 总结 452.用最少数量的箭引爆气球文档讲解:代码随想录用最少数量的箭引爆气球视频讲解:手撕用最少数量的箭引爆气球题目:学习:根据题干,很直观的贪心逻辑就是尽可......
  • 区间取值
    题目链接:https://bzoj.org/p/P00324Description给你三个数n,l,r,让你在[l,r]中找到一个整数x,使xmodn的值尽可能的大,输出这个最大的值。Input一行给出三个数字n,l,r2≤n≤l≤r≤1e9Output如题Samples输入数据171623输出数据16Hint在区间[16,23]之间可取20......
  • 区间更新、求和问题
    树状数组、线段树的实现与应用、模运算的使用。一、题目  定义在数组上的操作,给定大小为N的数组以及N个数。之后给出M个操作,则要输入M行。每一行操作第一个数代表操作类型,要么是U要么是C。对于U类型的操作,紧跟3个数a、b、k,也就是把数组下标a到b的每个数替换为该数的k次方(......
  • 树状数组和线段树板子
    树状数组板子#define_CRT_SECURE_NO_WARNINGS#include<iostream>#include<vector>#include<algorithm>#include<math.h>#include<sstream>#include<string>#include<string.h>#include<iomanip>#include<stdlib.h&g......
  • 区间DP
    区间DP对一段连续的区间进行动态规划,使其达到预期特点合并:即将两个或多个部分进行整合,当然也可以反过来;特征:能将问题分解为能两两合并的形式;求解:对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。特别——链变环对于原区......
  • 2023-2025年最值得选择的Java毕业设计选题大全:1000个热门选题推荐✅✅✅
    ......
  • java 查询日期列表月末对应上月末,季度末对应上季度末,年末对应上年末,取列表月度,季度,年
    packagecom.dc.galaxydata.model;importcom.dc.common.util.DateUtil;importjava.util.ArrayList;importjava.util.Date;publicclassEndDates{publicstaticvoidmain(String[]args){ArrayList<Date>dateList=newArrayList<>(......