树状数组:快速(动态)求前缀和
在线算法(支持修改)
O(logn)
1:给某个位置上的数加上一个增量(修改某个元素)
2:求某个前缀和
即:单点修改,区间查询(sum(r)-sum(l-1))
树状数组只能支持这俩问题,别的问题都得通过转化才能解决
比如区间修改,单点查询
需要利用差分转化为区间修改
记树状数组为C[x],定义:
C[x]=(x-lowbit[x],x],即x-lowbit[x]+1~x,奇数为其本身
lowbit[x]=x&-x,含义是2^k,k为x的二进制表示末尾有k个0
树状数组可以画成一棵树(每个节点只有一个父节点),x的父节点为x+lowbit(x),x的子节点为x-lowbit(x)
求前缀和很简单,递归找子节点累加即可:C[x]+=C[x-lowbit[x]],递归边界是x-lowbit[x]=1
int add(int x){
int res=0
for(int i=x;!i;i-=lowbit(x)) res+=c[i];
return res;
}
单点修改的话需要修改这个点的所有父节点,x的父节点为x+lowbit(x)
void query(int x,int v){
for(int i=x;i<=N;i+=lowbit(x)) c[i]+=v;
}
初始化:c[i]+=a[i]
单点修改(c[i]改成c) c[i]+=c-c[i]
求逆序对:(以下部分转载自下面帖子)
树状数组(求逆序对)_baby的我的博客-CSDN博客_树状数组求逆序对
先离散化
#include<bits/stdc++.h>
#define M 500005
using namespace std;
int a[M],d[M],t[M],n;
//原数组/ 离散化后的数组/ 树状数组
bool cmp(int x,int y)
{
if(a[x]==a[y]) return x>y;//避免元素相同
return a[x]>a[y];//按照原序列第几大排列
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],d[i]=i;//初始化
sort(d+1,d+n+1,cmp);
//排序时候d就是离散化的数组了
return 0;
}
转换之后,空间复杂度就没这么高了,但不是求d中的逆序对了,而是求d中的正序对
原因是离散化的数组存的是第i大的数在什么位置
比如原序列a={5,3,4,2,1}
首先把1放到树状数组t中,此时t只有一个数1,t中比1小的数没有,sum+=0
再把3放到树状数组t中,此时t只有两个数1,3,比3小的数只有一个,sum+=1
把2放到树状数组t中,此时t只有两个数1,2,3,比2小的数只有一个,sum+=1
把4放到树状数组t中,此时t只有两个数1,2,3,4,比4小的数有三个,sum+=3
把5放到树状数组t中,此时t只有两个数1,2,3,4,5,比5小的数有四个,sum+=4
最后算出来,总共有9个逆序对,可以手算一下原序列a,也是9个逆序对,
再求和
树状数组t表是的不是前缀和了,t[x]表示的是[1,x]中有几个数已经存在,这样我们每次把一个新的数x放进去的时候,都需要把包含这个数的结点更新,然后查询[1,x-1]有几个数已经存在
还是拿上一个例子:
把1放进去,包含t[1]的结点t[1]++,t[2]++、t[4]++, 由于n==5,算到t[8]的时候就已经跳出,查询[1,1-1]中比他小的数为0
把3放进去, 包含t[3]的结点t[3]++, t[4]++ ,然后查询[1 , 3-1]中有几个数已经存在,t[2]==1, sum+=1,lowbit一发等于0跳出
把2放进去, 包含t[2]的结点t[2]++, t[4]++ , 然后查询[1 , 2-1]中有几个数已经存在,t[1]==1, sum+=1,lowbit一发等于0跳出
把4放进去,包含t[4]的结点t[4]++, t[8]大于n 跳出, 查询[1 , 4-1]中有几个数已经存在,t[3]==1, sum+=1,lowbit一发t[2]==2,所以sum+=2
把5放进去, 包含t[5]的结点t[5]++, t[6]大于n跳出 , 查询[1 , 5-1]中有几个数已经存在,t[4]==4, sum+=4,lowbit一发等于0跳出
最后答案就出来了,关键是要理解那句标了红色的那句话,不是前缀和,而是有几个数已经存在,假如a[8]等于4,那么就表示[1,8]中只有4个数存在。