今天重温了一下树状数组,资料链接:https://oi-wiki.org/ds/fenwick/#_2
树状数组和线段树非常类似,不过树状数组的代码量比线段树小太多了,而且不容易出bug,所以学习树状数组还是非常有必要的。
树状数组主要理解这几个要点:①lowbit含义及原理②差分的思想
一、lowbit含义
lowbit返回值为,我们知道树状数组除了叶子结点其余结点都是记录一个区间的信息的,在线段树中我们通过传递结点控制的左右子树来递归处理区间的信息,而树状数组结点掌握的区间信息则需要通过lowbit来进行计算。
lowbit主要计算结点x的二进制从右往左数第一个1及其右边所有0组成的十进制数,举个栗子,,则
所以结点88能控制8个原序列的信息,为什么就能计算出控制的个数呢?因为按照二进制,负数的二进制是正数的二进制取反后+1,所以
,正好进行逻辑与运算后只剩最后四位,也就lowbit所要计算的数。
二、差分的思想
树状数组在进行区间加(区间修改)操作的时候,因为树状数组并没有像线段树一样使用标记数组来对修改的信息进行标记,所以这里使用差分的思想可以将区间修改转化为点修改
令,我们可以发现,区间加的时候,例如
区间内所有数都加
,差分的优势在于
区间内
的值是不变的,所以我们不用修改
的b值,只用修改l和r+1处的b值即可,原
,现
,所以我们要对r+1处的b值加上-x,同理l处b值应该加上x。
接下来将区间求和转化为差分后数组的求和公式,
则
第一项用树状数组维护,第二项用树状数组维护
例题1:
题目链接:https://loj.ac/problem/132
刚才的讲解其实就是题解QAQ
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#include<bits/stdc++.h> using namespace std; typedef long long LL; #define N 1000010 int n,q; LL a[N],b[N],f[N]; int lowbit(int x) { return x&(-x); } void add1(int x,LL y) { for (int i=x;i<=n;i+=lowbit(i)) b[i]+=y; } void add2(int x,LL y) { for (int i=x;i<=n;i+=lowbit(i)) f[i]+=y; } LL query1(int x) { LL ans=0; for (int i=x;i>0;i-=lowbit(i)) ans+=b[i]; return ans; } LL query2(int x) { LL ans=0; for (int i=x;i>0;i-=lowbit(i)) ans+=f[i]; return ans; } int main() { int opt,x,y; LL z; scanf("%d%d",&n,&q); for (int i=1;i<=n;++i) cin>>a[i],add1(i,(LL)(a[i]-a[i-1])),add2(i,(LL)i*(LL)(a[i]-a[i-1])); while (q--) { scanf("%d%d%d",&opt,&x,&y); if (opt==1) cin>>z,add1(y+1,-z),add1(x,z),add2(x,(LL)x*z),add2(y+1,(LL)(y+1)*(-z)); else cout<<query1(x-1)*(LL)(y-x+1)+(query1(y)-query1(x-1))*(LL)(y+1)-query2(y)+query2(x-1)<<endl; } return 0; } |
例题2:(树状数组+离散化求逆序对)
题目链接:https://www.luogu.com.cn/problem/P1908
题解:先离散化,将序列的值调整在1~n范围内,然后将离散后的值倒着插入树状数组,每次插入查询比当前数小的值有多少个,将这些数求和即是答案
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include<bits/stdc++.h> using namespace std; typedef long long LL; #define N 500010 int n,f[N]; LL ans; struct data { int val,id; }a[N],b[N]; bool cmp(data x,data y) { return x.val==y.val?x.id<y.id:x.val<y.val; } bool cmp2(data x,data y) { return x.id<y.id; } int lowbit(int x) { return x&(-x); } void add(int x,int y) { for (int i=x;i<=n;i+=lowbit(i)) f[i]+=y; } int query(int x) { int sum=0; for (int i=x;i>0;i-=lowbit(i)) sum+=f[i]; return sum; } int main() { scanf("%d",&n); for (int i=1;i<=n;++i) scanf("%d",&a[i].val),a[i].id=i; sort(a+1,a+n+1,cmp),b[1].val=1,b[1].id=a[1].id; for (int i=2;i<=n;++i) { b[i].id=a[i].id; if (a[i].val==a[i-1].val) b[i].val=b[i-1].val; else b[i].val=b[i-1].val+1; } sort(b+1,b+n+1,cmp2); for (int i=n;i;--i) add(b[i].val,1),ans+=(LL)query(b[i].val-1); return cout<<ans,0; } |
树状数组O(n)建树+求第k大(待UPD)