树状数组
树状数组是一个查询和修改复杂度都为O(logn)的数据结构,主要解决动态数组前缀和、区间和。
前缀和、修改数组元素
a1,a2,a3…an
询问a1+a2+a3+…+am
修改ai(1<=i<=n)
暴力:复杂度O(n^2)
树状数组:d[6]=a5+a6;
110 2^1
d[8]=a1+…+a8;
1000 2^3
如下图,若要求14位置的前缀和,仅需要计算d[14]+d[12]+d[8],每次查询不超过logn个位置。单次查询复杂度为O(logn)
查询13这个位置的前缀和:
13=1101
根据二进制拆分:
1101 d[13] 其范围内有2^0个元素
1100 d[12] 2^2
1000 d[8] 2^3
修改数组值
修改d[5]:
倒回去求出覆盖d[5]的区间–在最后一个1的位置加一
101 d[5]
110 d[6]
1000 d[8]
区间和
区间和可转化为前缀和进行计算: [5,9]–>sum(9)-sum(5)
lowbit运算
lowbit (int x){
return x&(-x);
}
-x=~x+1(取反加一)
1100 12
补码:0011+1 0100
1100
0100
0100=4
通过X&(-x)可得到最后一个1所在的位置,从而得出lowbit(x)
减去0100得到下一次查询的位置 1000 ,通过加减lowbit(x)可实现在各个位置之间的跳跃
#include<iostream>
using namespace std;
int d[10002];
int n;
//****************
int lowbit(int x){
return x&(-x);
}
int sum(int x){
int res=0;
while(x){
res+=d[x];
x-=lowbit(x);
}
return res;
}
void add(int x,int v){//在d[x]加上v
while(x<=n){
d[x]+=v;
x+=lowbit(x);
}
}
//****************
int main()
{
int m;
cin>>n;
for(int i=1;i<=n;i++)
add(i,i);//向树状数组中添加元素
while(scanf("%d",&m)==1){
//输出前缀和sum(m)
cout<<sum(m)<<endl;
}
return 0;
}
//二维树状数组
/*int d[301][301];
void update(int x,const int &y,const int &v)
{
for(;x<=n;x+=(x&(-x)))
for(int j<=n;j+=(j&(-j)))
d[x][j]+=V;
}
int getsum(int x,const int &y)
{
int res=0;
for(;x;x-=(x&(-x)))
for(int j=y;j;j-=(j&(-j)))
res+=d[x][j];
return res;
}*/
树状数组的应用–求逆序数对
面试题51. 数组中的逆序对
难度困难168收藏分享切换为英文关注反馈
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
class Solution {
public:
vector<int> res;
int tree[50005];
int n;
int lowbit(int i){
return i&(-i);
}
void add(int i,int x){
while(i<=n){
tree[i]+=x;
i+=lowbit(i);
}
}
int getsum(int i){
int res=0;
while(i>0){
res+=tree[i];
i-=lowbit(i);
}
return res;
}
int reversePairs(vector<int>& nums) {
//离散化数组
for(int i=0;i<nums.size();i++)
res.push_back(nums[i]);
sort(res.begin(),res.end());//从小到大排序
//去重
res.erase(unique(res.begin(),res.end()),res.end());
int cnt=0;
n=res.size();
for(int i=nums.size()-1;i>=0;i--){
int k=lower_bound(res.begin(),res.end(),nums[i])-res.begin()+1;
cnt+=getsum(k-1);
add(k,1);
}
return cnt;
}
};
离散化:相对于数组中的值,我们其实更关心的是数组中的元素的“相对排名”,为了避免开辟多余的“树状数组”空间,我们首先对数组元素做预处理,这一步叫“离散化”;
具体的做法是:
先离散化,将所有的数组元素映射到 0、1、2、3… ,这是为了节约树状数组的空间;
从后向前扫描,边统计边往树状数组里面动态添加元素