(萌新第一次发文,请大佬指正)php
要了解树状数组,首先须要了解它是用来作什么的.
那么:node
如图:
忽然一看可能难以理解,那么是什么把它们联系起来的呢?
接下来介绍lowbit函数c++
这里用到了补码的原理:数组
即负数的补码为其二进制绝对值取反+1,而当其与其绝对值取&操做时获得的刚好就是其二进制最后一位1的位置函数
例如:00001000(8)的负数补码为11111000(-8) 与操做后为00001000优化
而lowbit(8)的值就为 00001000;this
则 lowbit函数代码spa
inline int lowbit(int x) { return x & -x; }
这里就稍微偏个题顺便介绍一下inline(内联函数)code
C++关键字,在函数声明或定义中函数返回类型前加上关键字inline,便可以把函数指定为内联函数。
在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。
此关键字仅是对编译器的一种建议,并不是强制.对于内容较短且无循环的代码,inline的使用能够增长函数运行的效率,但其效率的增长是以代码长度为代价,因此,仅在短且无循环的函数前可使用,其余状况避免使用.blog
那么lowbit函数的用处是什么呢.
让咱们以二进制思惟稍微注意一下能够发现,每一个序号的二进制数的后导0的个数,恰为其包含的数组的深度(自底向上的),设其为d,那么其包含的范围就是包括它向前的2^d个数.如100(4)为从100向前4个原数组中的数的和,而110(6)是从110向前2个原数组中的数的和.
咱们令原数组为a[],树状数组为tree[].
这时,咱们能够理解tree数组中存储的究竟是什么数据,
那么对于以上问题模型,咱们该如何求解呢.
咱们要更新原数组中某一个数,那么就得更新树状数组中全部包含它的数据,
那么,都有哪组数据包含它呢,也就是寻找这个子节点的全部根节点的特色.
这时,就又轮到咱们的lowbit函数上场了,x的全部父节点只需x + lowbit(x)
就能够获得.
那么update函数的代码(时间复杂度O(logn)):
//x为须要修改的节点,v为a[x]增长的值,n是树状数组的最大范围. void update(int x,int v) { for(;x <= n;x += lowbit(x)) tree[x] += v; }
对于树状数组tree,咱们能够了解其存储的值实质上是某一区间的前缀和.
咱们查询某一段区间(x,y)的值,就只须要求出y的前缀和减去x-1的前缀和
这样获得的,就是区间(x,y)的和.
那么(1,x)区间的前缀和怎么求呢,仍然是lowbit函数,只须要作一遍更新的逆过程,
即计算tree数组(x->1)的和就能够求出全部不重合的区间前缀和.
为何tree(x->1)就是该区间的前缀和?
在二进制的视角下,x的数位上每个1,都表明其一段域内的值,
那么,每一个1都必然与其余1的范围不重合例如:tree[110] = tree[100] + tree[10]
如此,query的代码就很明了了.
int query(int x) { int res = 0; for(;x > 0;x -= lowbit(x)) res += tree[x]; return res; }
板子题连接(luoguP3374)
到此为止,咱们讲解的就是PUIQ模型,即点更新,段查询.
怎么样代码是否是短到怀疑人生,这在赛场上写起来不是舒舒服服?
那么,接下来的IUPQ模型,就须要一个切入点来完成操做了.
当咱们想要进行段更新时,那么若是仍然用上面的代码,就不得不添加一个for循环,
此时update函数的时间复杂度会上升到O(nlogn),那么,有没有什么方法来优化操做呢?解决方案就是--差分
差分,也就是定义一个差分数组b来存储a数组的差值,而tree做为数组b的树状数组.
即:
b[1] = a[1] b[2] = a[2] - a[1] b[4] = a[4] - a[1] tree[1] = b[1] tree[2] = b[2] + tree[1] tree[4] = tree[2] + tree[3] + b[4]
那么这时的a数组值该如何获得呢?
对于b数组,a[n]
就等于b[1] +...+b[n]
而query函数恰好就是用来求b[n]的前缀和,也就是a[n]的值.
那么更新操做呢,对于差分数组b,若咱们想要更新[l,r]
范围内的值+v,那么咱们只须要将b[l]
更新为b[l] + v
就表明着a[l] - a[n]
全部数都+1,
而在b[r+1]
处将+v的效果消除,即b[r+1] - v
那么如何将这个操做更新在tree数组中,就是上文update的事情了.
即只需更新b[l]+v,b[r+1]-v便可
update(l,v); update(r+1,-v);
复杂度一样为O(logn)
板子题连接(luoguP3368)
问题模型,给定n个整数,求出逆序整数对数(即a[u] > a[v] && u < v
的整数对)
看见题目咱们就能够写出O(n2)的暴力for来求解,可是如何将其用树状数组来优化到O(nlogn)呢
这里用到桶排序的思想.
首先让问题简单化一点,让n个整数a[i]均小于等于100.
那么咱们就能够开tree[105]的数组,每次读入更新一个数时,只需update(a[i],1)
此时,比a[i]大的数字就是须要更新的逆序对对数,即query(a[100]) - query(a[i])
由于当前共读入了i个数,那么求值亦可简化为i - query[a[i]]
也就是读入一轮+每次查询就可获得总共的逆序对对数.时间复杂度O(nlogn)
那么当数据足够大时,咱们的数组存不下的时候呢,这时候,就轮到咱们的核心思想,离散化出场了.
对于离散化,我的理解就是因为想要利用桶数组,故将数据相对缩小(保证相对大小不变)到能够开到的数组那么大而减少空间需求.
那么到底该如何实现,请看以下代码:
离散化核心代码: struct node { int v;//数值自己 int order;//原序列的的下标 }a[500005]; int dis[500005]; //用来存储原数第i个数的order下标是什么 sort(a,a+n,cmp); //注意须要由大到小排 for(int i = 1;i <= n;++i) dis[a[i].order] = i;
原理很简单就只是按a[i].v的大小重排,并从新赋予他们相对大小不变,总体缩小的新的a[i].v
具体代码以下:(HDU2689)
#include <bits/stdc++.h> #define mem(n) memset(n,0,sizeof(n)) using namespace std; int n; struct node{ int val,order; bool operator < (const node & x) const{ return this->val < x.val; } }a[1005]; int dis[1005]; //差分数组 int tree[1005]; inline int lowbit(int x) { return x & -x; } void update(int x,int v) { for(;x <= n;x +=lowbit(x)) tree[x] += v; } int query(int x) { int res = 0; for(;x > 0;x -= lowbit(x)) res += tree[x]; return res; } int main() { while(~scanf("%d",&n)){ mem(a); mem(dis); mem(tree); for(int i = 1;i <=n;++i){ scanf("%d",&a[i].val); a[i].order = i; } sort(a + 1, a + 1 + n); for(int i = 1;i <= n;++i){ dis[a[i].order] = i; } int cnt = 0; for(int i = 1;i <= n;++i){ update(dis[i],1); cnt += i - query(dis[i]); } printf("%d\n",cnt); } return 0; }