day1-树状数组

树状数组

树状数组是一个查询和修改复杂度都为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… ,这是为了节约树状数组的空间;
从后向前扫描,边统计边往树状数组里面动态添加元素

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值