cakephp update query返回空数组_树状数组

树状数组

BY RBCORN

1.前缀和

​ 为了方便区间求和,前缀和应运而生。

​ 具体来说,前缀和是一种预处理操作。在得到数据后,对于数组里的每个位置 i , 前缀和pre[i]代表数组从头到当前位置的求和,即

。那么[l,r]区间的求和很明显就是pre[r]-pre[l-1]。

​ 计算前缀和时,为从1到i直接累加

​ 而前缀和的查询为O(1),但是修改则需要O(n)。

​ 此时我们需要一个更快速的方法——树状数组

2.树状数组

具体思想

​ 树状数组同样是一种预处理操作

​ 树状数组,顾名思义,是一个数组对应着一棵树,大概如此:

b2ee16adfcfaba2197e3144c62ad3e86.png

​ 显而易见,数组中每个位置对应的树的结点中,只有最上层存放数据,而它的更新和查询,都是靠着上面这棵树。在这颗树中,树中位置i所对应的亮点记作tree[i]。暗点的值不做记录。

​ 先是更新。将位置i的值增加一定的量x,则要在树状数组里一直向上,把途径的每个亮点都加上x。

3c2c7b2bdceddd3b7dd137fad38285d0.gif

​ 容易知道每个tree[i]中存储的是以树中结点i为根的子树的叶子节点a[j]之和,

​ 根据上面这句话,我们可以查询前缀和:

f08306330f82444b59c78545bb538049.gif

​ 在这个例子中,pre[3]=tree[2]+tree[3]。

​ 现在我们可以知道,更新和查询这两种操作,时间复杂度都为O(log n)。

​ 对比前缀和,显然在大部分情况下此方法更快。

代码实现

lowbit:

​ lowbit(x)是x的进制表达式中数位最低的1所对应的值。例如 lowbit((1000101100)_2)=(100)_2

​ lowbit(x)=x&(-x)。一个数的相反数就是其取反加一所得到的值,若末尾有一些0,则取反之后都会变成1,再加一之后就会从右往左不断进位,直到遇上第一个0(原数中的1),进位停止,到此时,从数位最低的1开始,后面的数(包括1)都与原数相同,而1之前与原数不同,实现了lowbit操作。

更新操作:

​ 现在需要解决的问题是:如何找到一个点的父结点?这里就需要用到lowbit。在树状数组中,寻找父节点,就是逐渐向高位,向上取整。可以看几个例子:

​ 0001->0010->0100->1000

​ 0011->0100->1000

​ 而向更高一位且向上取整,就是让一个数加上自己的lowbit。因为在二进制中1+1=10正好进位,所以加上去的lowbit与原数中的lowbit相结合,便产生了进位,能够向更高位并向上取整。而更高位可能又产生进位(例如11可能本身想要取整到第二位,但是第二位又产生了进位,所以是100)。

(这一步理解起来比较困难,可以自己试着举几个例子)

更新时,找到了父节点,更新它后,需要找父节点的父节点,继续更新,直到根节点(根节点也需要更新)

void update(int pos,int val){//更新操作 pos=需要更新的位置 val=在当前位置加上的值
    while(pos<=n){
        tree[pos]+=val;//更新当前结点
        pos+=lowbit(pos);//寻找父节点
    }
}

查询操作:

​ 查询前缀和。

​ 查询中,我们需要找到几个树中的结点,能够涵盖从1到当前位置i中所有值的和。

​ 思路与插入类似,不过这次是不断向高位并向下取整。这样做,类似于找一个二进制数中有哪几位为1。找到一个1后,就可以把它去掉了,也就是减去目前的lowbit,然后累加树结点中的值,得到答案。看上面的动画就能够理解这点。

​ 代码如下:

int query(int pos){//pos的前缀和
    int ans=0;//记录答案
    while(pos){//相当于pos!=0
        ans+=tree[pos];//累加答案
        pos-=lob(pos);//寻找下一节点
    }
    return ans;
}

完整程序:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define rep(i,e) for(int i=1;i<=e;i++)
#define mmm(a,x) memset(a,x,sizeof(a))
#define rep_e(i,p,u) for(int i=head[p],u=to[i];i;i=nxt[i],u=to[i])
#define lob(i) (i&(-i))
typedef long long LL;
const int maxn=5e5+9;
LL n;
LL a[maxn],tr[maxn];//tr=tree
void update(int pos,LL val){
    while(pos<=n){
        tr[pos]+=val;
        pos+=lob(pos);
    }
}
LL query(int pos){
    LL ans=0;
    while(pos){
        ans+=tr[pos];
        pos-=lob(pos);
    }
    return ans;
}

int main(){
    cin>>n;//数组大小
    rep(i,n){
        cin>>a[i];
        update(i,a[i]);
    }
    int op; cin>>op;//op=1更新 op=2查询
    if(op==1){
        int pos; LL val;
        cin>>pos>>val;
        update(pos,val);
    }
    else{
        int r,l;
        cin>>l>>r;
        cout<<query(r)-query(l-1)<<endl;
    }
    return 0;
}

e8a76485b21c148b8e10072277906d8f.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值