lowbit运算、树状数组详解

lowbit运算

lowbit(x)=x&(-x)

lowbit(x)可以理解为能整除x的最大2的幂次

树状数组

存放的是i号位之前(含i号位,下同)lowbit(i)个整数之和

C[i]的覆盖长度是lowbit(i)[也可理解为管辖范围]

将C[i]画成二维图容易理解

树状数组的下标必须从1开始

C[x]=A[x-lowbit(x)+1]+···+A[x]

getSum(x)返回前x个数之和

C[x]=A[x-lowbit(x)+1]+···+A[x]可推得

SUM(1,x)=SUM(1,x-lowbit(x))+C[x]

int getSum(int x) {//返回前x个整数的和
    int sum = 0;//用于记录和
    for (int i = x; i > 0; i -= lowbit(i)) {
        sum+=c[i];
    }
    return sum;
}

**技巧:**如果要求数组下标在区间[x,y]内的数之和,可以转换为getSum(y)-getSum(x-1)解决

update(x,v)实现将第x个数加上一个数v的功能,即A[x]+=v

寻找树状数组C中能覆盖A[x]的元素,让它们都加上v

找到距离当前C[x]的最近的能覆盖C[x]的C[y]:lowbit(y)必须大于lowbit(x):

找一个尽可能小的整数a使得lowbit(x+a)>lowbit(x) ,最小的a为lowbit(x)

void update(int x,int v){
    for(int i=x;i<=MAXV;i+=lowbit(i)){//注意i必须能取到N
        c[i]+=v;//让c[i]加上v,然后c[i+lowbit(i)]加上v
    }
}
统计序列中在元素左边比该元素小的元素个数

思路1

hash数组:hash[x]记录整数x当前出现的次数,从左到右依次遍历序列A,假设当前访问的是A[i]则令hash[A[i]]++,则在序列中在A[i]左边比A[i]小的数个数等于hash[1]+hash[2]+···+hash[A[i]-1]

思路2

使用update(A[i],1)和getSum(A[i]-1)解决

#include<cstdio>
#include<cstring>

const int maxn = 10010;
#define lowbit(i) ((i)&(-i)) //lowbit写成宏定义的形式,注意括号
int c[maxn];//树状数组

//update函数将第x个整数加上v
void update(int x,int v){
    for(int i=x;i<maxn;i+=lowbit(i)){//i<maxn或i<=n都可
        c[i]+=v;//让c[i]加上v,然后让c[i+lowbit(i)]加上v
    }
}

//getSum返回前x个整数之和
int getSum(int x){
    int sum=0;//记录和
    for(int i=x;i>0;i-=lowbit(i)){//注意是i>0:树状数组的下标从1开始
        sum+=c[i];//累计c[i],把问题缩小为SUM(1,i-lowbit(i))
    }
    return sum;//返回和
}

int main(){
    int n,x;
    scanf("%d",&n);
    memset(c,0,sizeof(c));//树状数组的初值为0
    for(int i=0;i<n;i++){
        scanf("%d",&x);//输入序列元素
        update(x,1);//x的出现次数加一
        printf("%d\n",getSum(x-1));//查询当前小于x的数的个数
    }
    return 0;
}

边界问题的考虑

当A[i]<=N不成立时

把A[i]与1~N对应起来

解决方法:离散化:设置一个临时的结构体数组,存放输入的序列元素的值以及原始序号,在输入完毕后数组按val从小到大排序,排序完后按照“计算排名”的方式将“排名”根据原始序号pos存入一个新数组中。离散化适用于离线查询

对于在线查询,可以先把所有操作记录下来,然后对其中出现的数据进行离散化,之后按记录下来的操作顺序正常进行“在线查询”

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int maxn = 100010;
#define lowbit(i) ((i)&(-i)) //lowbit写成宏定义的形式,注意括号

struct Node {
    int val;//序列元素的值
    int pos;//原始序号
} temp[maxn]; //temp数组临时存放输入数据

int A[maxn];//离散化后的原始数组
int c[maxn];//树状数组

//update函数将第x个整数加上v
void update(int x, int v) {
    for (int i = x; i < maxn; i += lowbit(i)) {//i<maxn或者i<=n都可以
        c[i] += v;//让c[i]加上v,然后让c[i+lowbit(i)]加上v
    }
}

//getSum函数返回前x个整数的和
int getSum(int x) {
    int sum = 0;//记录和
    for (int i = x; i > 0; i -= lowbit(i)) {//注意是i不是i>=0
        sum += c[i];//累计c[i],把问题缩小为SUM(1,i-lowbit(i))
    }
    return sum;//返回和
}

//按val从小到大排序
bool cmp(Node a, Node b) {
    return a.val < b.val;
}

int main() {
    int n;
    scanf("%d", &n);
    memset(c, 0, sizeof(c));//树状数组初始值为0
    for (int i = 0; i < n; i++) {
        scanf("%d", &temp[i].val);//输入序列元素
        temp[i].pos = i;//原始序号
    }
    //离散化
    sort(temp, temp + n, cmp);//按val从小到大排序
    for (int i = 0; i < n; i++) {
        //与上一个元素值不同时,赋值为元素隔宿
        if (i == 0 || temp[i].val != temp[i - 1].val) {
            A[temp[i].pos] = i + 1;//必须从1开始!树状数组的性质
        } else {//与上一个元素值相同时可以直接继承
            A[temp[i].pos] = A[temp[i - 1].pos];
        }
    }
    //正式进入更新和求和的操作
    for (int i = 0; i < n; i++) {
        update(A[i], 1);//A[i]的出现次数加1
        printf("%d\n", getSum(A[i] - 1));//查询当前小于A[i]的数的个数
    }
    return 0;
}
使用树状数组求解序列第K大问题
//求序列元素第K大
int findKthElement(int K){
    int l=1,r=maxn,mid;//初始区间为[1,maxn]
    while(l<r){//循环,直到[l,r]能锁定单一元素
        mid=(l+r)/2;
        if(getSum(mid)>=K)  r=mid;//所求位置不超过mid
        else l=mid+1;//所求位置大于mid
    }
    return l;//返回二分夹出的元素
}
给定二维整数矩阵A,求A[1] [1]~A[x] [y] 子矩阵的所有元素和,给单点A[x] [y] 加上整数v

求A[a] [b]~A[x] [y] 这个子矩阵的元素之和:计算getSum(x,y)-getSum(x-1,y)-getSum(x,y-1)+getSum(x-1,y-1)

对于高维只需要把for循环改为相应的重数

int c[maxn][maxn];
//二维update函数位置(x,y)的整数加上v
void update(int x,int y,int v){
    for(int i=x;i<maxn;i+=lowbit(i)){
        for(int j=y;j<maxn;j+=lowbit(j)){
            c[i][j]+=v;
        }
    }
}

//二维getSum函数返回(1,1)到(x,y)的子矩阵中元素之和
int getSum(int x,int y){
    int sum=0;
    for(int i=x;i>0;i-=lowbit(i)){
        for(int j=y;j>0;j-=lowbit(j)){
            sum+=c[i][j];
        }
    }
    return sum;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值