树状数组
一、模板
默认预设:
a[maxn] ———原始数组
c[maxn] ———新建的树状数组。对于任意一个位置 i ,树状数组 c[i] 里的值就是"它能管理到的所有位置上,原数组的值之和"。
基本函数一:lowbit
int lowbit(int x)
{
return x&(-x);
}
x+lowbit(x),就可访问到x的上一级父节点;
基本函数二:upgrade
当我们修改a数组中的某一个值时 应当如何更新c数组呢?答案就在这里
upgrade函数,用于单点更新:当原数组a中,下标为x的位置被+k的时候,只需要把"能管理到x的所有位置"都+k就行
调用函数:upgrade(c,x,k)
void upgrade(int *arr,int x,int n)//n为:树状数组的节点总数,也是原数组的节点总数
{ //k为:当前被更新的节点,在原数组a中的下标值
while(x<=n)
{
arr[x]+=k;
x=x+lowbit(x);
}
}
ps:经常用这个函数,来构建初始树状数组。一边读入原数组a,一遍就更新树状数组c
构建树状数组的时候:自底向上,每一个数都把它能做的贡献做了。
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
int k=i;
while(k<=n)
{
c[k]+=a[i];
k=k+lowbit(k);
}
}
基本函数三:getsum
用于区间查询:查询原数组 a 从1~x 区间上的和。利用c[i]数组,求a数组中前i项的和。
利用树状数组对a求和的时候:自顶向下
调用函数:getsum(c,x)
int getsum(int *arr,int x)
{
int ans=0;
while(x>0)
{
ans+=arr[x];
x-=lowbit(x);
}
return ans;
}
拓展应用:也可以被用来查询任意区间l~r上的和,只需sum(r)-sum(l) 即可。
模板使用时的注意点 && 进阶小技巧
1):所有数组,无论是 a [i] , c[i] ,还是 cx [i] 数组,一律从下标 1 开始存起,下标为0的位置不存数据。
这一点很重要,如果下标从0开始,那么getsum中while(x>0)就必须改成while(x>=0)(因为0号位的数据也要加进去嘛)
但问题是,0+lowbit(0) ,永远都是0。这样一来,在 0 处就会产生死循环,
2):如何确定,upgrade(c,x,k)中的x、k参数,即:传入哪个下标,每个位置加多少??
确定传入的下标x:从哪个下标开始,此下标之后所有位置 i 的sum(i),都会受到a[x]被更新的影响?
确定每一个结点加的数k:此次focus的结点,对最终答案sum的贡献,有多大?
一般而言,k都是原数组a[i]的数据值本身。
3):getsum(c,x)得出的累加和,是包含了原数组中a[x]的。(末尾数据也算进了getsum返回的累加和)
4):到底是先upgrade,还是先getsum?:这取决于,调用sum(x)时,你想要求的,是“小于”还是“小于等于”x的累加和。
如果有“等于”,要把x位置加上去,那就先upgrade,先更新过再加;否则,如果不要把x位置加上去,那就先getsum,趁
着还没x位置还没被加到数组上去,先sum
二、基本题型(以及三个基本模型)
模型一:原数组a[i]的数据域,存储的就是数值本身,用于sum来求:区间l到r上的所有数字的累加和
模型二:原数组a[i]的数据域,存储的是“差分值”,用于sum来求:插段求点
模型三:原数组a[i]的数据域,不存储具体的数值,而存储“数值 i 出现的次数”,用于sum来求:“ 小于 ‘等于’ i 的数字,到目前为止,出现的次数之和”
一、插点求段 (更新单点,查询区间) ——————模型一
我的通常做法是:单点向上更新,sum(代表1~n的和)向下查询
专题10 1001
二、插段求点(更新的是 “区间” ,查询的是 “单点”————成段更新,单点查询) ——————模型二
差分思想。
原数组a[i],存储的是,第i个数值和第i-1个数值之差。
把“更新区间”转换成“更新两个点”
http://www.cnblogs.com/iris001999/articles/8926598.html
三、插段求段 ————模型二
依旧是差分。
小应用:
一、用树状数组求逆序对 ————模型三
由此引出树状数组的另一种常见模型:
原数组a[i],不存储具体的数值,而存储“数值 i 出现的次数”。
具体的数值,由下标表示。数组下标具有实际意义:代表数值大小。
例如,"a[i]"存储的值就表示,数值 i 出现的次数。
然后,构建原数组a[i]的树状数组c[i] , 最后sum(c , i)的结果,就代表:“ 小于 ‘等于’ i 的数字,到目前为止,出现的次数之和”。
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1556
扔下大佬的题解:https://www.cnblogs.com/xiongmao-cpp/p/5043340.html
AC代码:https://paste.ubuntu.com/p/N9SQrGK5Kx/
三、树状数组综合应用:
树状数组综合题的解题思路:
先想总体思路,先把最终的表达式子推出来。
因为树状数组归根结底,只是一种 累加的“具体实现”上的优化,并不是思路的东西。
然后看着你最终得出的那个式子
只要发现:有累加形式,并且累加式子里的每一项,是有规律的,是可以给出“通项公式”的,就代表此处可以优化。
而且,可以构建多个树状数组。多出来的树状数组,不会增加很多复杂度,只需要在更新的时候“顺带着更新一下多出来的其他树状数组”,就可以了。
看自己需求,需要几个树状数组,就维护几个树状数组就可以了。
经典题目:

浙公网安备 33010602011771号