树状数组

首先看一下朴素算法进行下面两个操作的时间复杂度

区间查询 O(n)                单值修改O(1)

如果进行n次查询,那么时间复杂度将是不可接受的

其次看一下前缀和数组的时间复杂度

区间查询 O(1)                单值修改O(n)

同样进行n次修改,时间复杂度也是不可接受的

树状数组则是在这两者中做了折中,实现了两者均为log(n)的时间复杂度

区间查询 O(log(n))                单值修改O(log(n))

lowbit

树状数组所学的预备知识

lowbit即求一个数的二进制表示的最低位的1

int lowbit(int x){ return x&(-x); } //x&(~x+1)  ~x+1=-x

树状数组

美妙的规律

下面构造数列t[x](前缀和的树状表示)

何为以x为根的子树种叶节点的值

注意如t[4],以t[4]为根的叶分别是t[2],t[3],a[4],也就是t[4]=t[2]+t[3]+a[4]

  

再来观察下t[x]所表示的区间——是以a[x]为结尾,长度为lowbit(x)的区间

观察上图 lowbit(1)=lowbit(3)=lowbit(5)=lowbit(7)=1

lowbit(2)=lowbit(6)=2        lowbit(4)=4        lowbit(8)=8

 

并且我们可以发现一个子叶x的父节点就是x+lowbit(x) ----------------(1)

掌握了上述规律后,就可以开始研究两个操作了:单点修改,区间求和

单点修改&&区间查询

单点修改

要修改a[x],也就是我们要将所有涵a[x]这个元素的t[i]修改了

注意下图,我们只需要找到最小的涵a[x]的子叶,然后再通过其找到父节点

下面给出代码 (运用规律(1),因为要通过子节点找父节点)

void add(int x,int k){
    while(x<=n){ t[x]+=k; x+=lowbit(x);}
}

区间查询

区间求和我们可以分两步

1.得到左右端点的前缀和                2.做差

int ask(int x){
    int ans=0;
    while(x){ ans+=t[x]; x-=lowbit(x); }
}

区间修改&&单点查询

让我们再研究两个操作:区间修改,单点查询;这时要引入增量数组b

用树状数组维护b的前缀和,即a[]每个元素的增量

[l,r]+k        add(l,k)        add(r+1,-k)

查询a[x]        ans=a[x]+ask(x)

区间修改&&区间查询

使用树状数组当然也是可以的,但是更好的当然是线段树了(之后再补)

 注意到下图中蓝色区域就是我们要求的

这样我们可以分别用2个树状数组t1,t2维护b[i]和i*b[i]的前缀和

 也就得到了下面公式了


例题

[模板]树状数组1

  (P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<bits/stdc++.h>
using namespace std;
int n,m,op,x,y;sum[500010],a[500010],b[500010];//sum表示a的前缀和用于初始化b
int lowbit(int x){ return (x&-x); }
void inti(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
    }
    for(int i=1;i<=n;i++){
        b[i]=sum[i]-sum[i-lowbit(i)];
    }
}
int ask(int x){//表示ai的前缀和
    int ans=0;
    while(x){ ans+=b[x]; x-=lowbit(x); }
}
void add(int x,int k){
    while(x<=n){ b[x]+=k; x+=lowbit[x]; }
}
int main(){
    inti();
    for(int i=0;i<m;i++){
        cin>>op>>x>>y;
        if(op==1)add(x,y);
        else cout<<ask(y)-ask(x-1)<<endl;
    }

    return 0;
}

[模板]树状数组2

#include<bits/stdc++.h>
using namespace std;
int lowbit(int x){ return (x&-x); }
//sum表示dis的前缀和,dis表示a的差分,a用于初始化b(树状数组)
int n,m,op,x,y,k,dis[500010],a[500010],b[500010],sum[500010];
void inti(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        dis[i]=a[i]-a[i-1];
        sum[i]=dis[i]+sum[i-1];
    }
    for(int i=1;i<=n;i++){
        b[i]=sum[i]-sum[i-lowbit(i)];
    }
}
int ask(int x){//表示a[i]
    int ans=0;
    while(x){ ans+=b[x]; x-=lowbit(x); }
    return ans;
}
void add(int x,int k){
    while(x<=n){ b[x]+=k; x+=lowbit(x); }
}
int main(){
    inti();
    for(int i=0;i<m;i++){
        cin>>op;
        if(op==1){
            cin>>x>>y>>k;
            add(x,k);
            add(y+1,-k);
        }
        else {
            cin>>x;
            cout<<ask(x)<<endl;
        }
    }
    return 0;
}

P5057 [CQOI2006]简单题

P5057 [CQOI2006]简单题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

首先用树状数组维护a的差分数组,a[i]表示元素i被修改了几次(那么偶数次为0,奇数次为1)

也就是ask(x)即得到a[i],对[l,r] +1,就是add(l,1),add(r+1,-1)

#include<bits/stdc++.h>
using namespace std;
int n,m,op,x,y,b[100010];
int lowbit(int x){ return x&(-x); }
int ask(int x){//求a[x]
    int ans=0;
    while(x){ ans+=b[x],x-=lowbit(x); }
    return ans&1;
}
void add(int x,int k){
    while(x<=n){ b[x]+=k,x+=lowbit(x); }
}
int main(){
    cin>>n>>m;
    for(int i=0;i<m;i++){
        cin>>op;
        if(op==1){
            cin>>x>>y;
            add(x,1),add(y+1,-1);
        }
        else{
            cin>>x;
            cout<<ask(x)<<endl;
        }
    }
    return 0;
}

P2068 统计和

 注意可能会爆int

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
ll n,m,x,y,b[100010];
char op;
ll lowbit(ll x){ return x&-x; }
void add(ll x,ll k){
    while(x<=n){ b[x]+=k,x+=lowbit(x); }
}
ll ask(ll x){
    ll ans=0;
    while(x){ ans+=b[x],x-=lowbit(x); }
    return ans;
}
int main(){
    cin>>n>>m;
    while(m--){
        getchar();//读取行末的回车
        cin>>op>>x>>y;
        if(op=='x') add(x,y);
        else cout<<ask(y)-ask(x-1)<<endl;
    }
    return 0;
}

P4939 Agent2

#include<bits/stdc++.h>
using namespace std;
int n,m,x,y,op,b[10000010];
int lowbit(int x){ return x&-x; }
void add(int x,int k){
    while(x<=n){ b[x]+=k,x+=lowbit(x); }
}
int ask(int x){
    int ans=0;
    while(x){ ans+=b[x],x-=lowbit(x); }
    return ans;
}
int main(){
    cin>>n>>m;
    while(m--){
        cin>>op>>x;
        if(!op){
            cin>>y;
            add(x,1);
            add(y+1,-1);
        } 
        else cout<<ask(x)<<endl;
    }
    return 0;
}

P5094 [USACO04OPEN] MooFest G 加强版

 题目所求即 \sum_{n \geqslant j > i\geqslant 1}^{n}max(v_i,v_j)*|j-i|

首先将牛按听力大小排序,遍历n头牛,每头牛计算与前面的牛的音量(即处理了max)

下面处理|j-i|,考虑第i头牛

用两个树状数组分别维护前i-1头牛中坐标小于a[i].x的个数cnt;

以及前i-1头牛中坐标小于a[i].x的牛的坐标和sum;

计算前i-1头牛的坐标总的tot

随后就可以求出音量和为(tot-2*sum+(2*cnt-i+1)*a[i].x)*a[i].v

!!!注意add里面x<=50000不是小于等于n        调了一万年!!!

!!!                                 注意开ll                                         !!!

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a1[50010],a2[50010],n,sum,cnt,ans,tot;
struct cow{ ll v,x; }a[50010];
bool cmp(cow c1,cow c2){ return c1.v<c2.v; }
ll lowbit(ll x){ return x&-x; }
ll ask(ll* ar,ll x){
    ll ans=0;
    while(x){ ans+=ar[x]; x-=lowbit(x); };
    return ans;
}
void add(ll* ar,ll x,ll k){
    while(x<=50000){ ar[x]+=k; x+=lowbit(x); };
}
int main(){
    //a1储存前i-1头牛中坐标小于等于a[i].x的牛的坐标和
    //a2储存前i-1头牛中坐标小于等于a[i].x的牛的个数
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i].v>>a[i].x;
    sort(a+1,a+n+1,cmp);
    add(a1,a[1].x,a[1].x);
    add(a2,a[1].x,1);
    tot+=a[1].x;
    for(int i=2;i<=n;i++){
        sum=ask(a1,a[i].x);
        cnt=ask(a2,a[i].x);
        ans+=(tot-2*sum+(2*cnt-i+1)*a[i].x)*a[i].v;
        add(a1,a[i].x,a[i].x);
        add(a2,a[i].x,1);
        tot+=a[i].x;
    }
    cout<<ans;
    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值