树状数组进阶 - 区间修改区间查询OA信用盘平台出租、二维树状数组

①首先是最基础OA信用盘平台出租【话仙源码论坛】hxforum.com【木瓜源码论坛】papayabbs.com的树状数组:

复制代码
//BIT - 单点增加,区间查询 - st
struct _BIT{
int N,C[MAXN];
int lowbit(int x){return x&(-x);}
void init(int n)//初始化共有n个点
{
N=n;
for(int i=1;i<=N;i++) C[i]=0;
}
void add(int pos,int val)//在pos点加上val
{
while(pos<=N)
{
C[pos]+=val;
pos+=lowbit(pos);
}
}
int sum(int pos)//查询1~pos点的和
{
int ret=0;
while(pos>0)
{
ret+=C[pos];
pos-=lowbit(pos);
}
return ret;
}
}BIT;
//BIT - 单点增加,区间查询 - ed
复制代码

原理:

假设我们现在要维护的是a数组,我们实际存储的是c数组,他们两者的关系如图:

    (称a数组为原数组,而c数组是a数组的树状数组。)

有:

复制代码
C1 = A1
C2 = A1+A2
C3 = A3
C4 = A1+A2+A3+A4
C5 = A5
C6 = A5+A6
C7 = A7
C8 = A1+A2+A3+A4+A5+A6+A7+A8
复制代码
c[i]不再是简单的存储a[i],而是存储了a[i]+a[i-1]+…+a[k],它存储了从a[i]往前若干个元素的和,那么如何确定k呢?这就关系到lowbit函数……

lowbit(x)函数返回的是什么?先看下图:

不难看出,lowbit(x)返回的是:若二进制下数字 xx 的尾部的零的个数为 k ,则lowbit(x) = 2k2k;

也就是说,c数组中,

  若i为奇数,c[i]=a[i]c[i]=a[i];

扫描二维码关注公众号,回复: 2368827 查看本文章

  若i为偶数,而其最多能整除k次2,c[i]=a[i]+a[i−1]+⋯+a[i−2k+1]c[i]=a[i]+a[i−1]+⋯+a[i−2k+1];

这样一来,对于某个pos点的增加xx,只要不断令pos+=lowbit(pos),就相当于一直往父亲节点走,所以我们在每个父亲节点都要增加xx。

②区间修改,单点查询BIT:

其实这个的原理就是:通过差分把这个区间修改、单点查询的问题转化为①;

首先,假设我们要记录的数组是a[1:n]a[1:n],那么我们假设有d[i]=a[i]−a[i−1]d[i]=a[i]−a[i−1],且d[1]=a[1]d[1]=a[1],

显然,就有a[i]=d[1]+d[2]+⋯+d[i]a[i]=d[1]+d[2]+⋯+d[i],

我们在BIT中实际存储的是数组d[1:n]d[1:n](准确的说是d数组的树状数组);

先说修改:

  我们目标是给a[L:R]a[L:R]全部加上xx,那么我们不难发现,其实d[L+1],d[L+2],⋯,d[R]d[L+1],d[L+2],⋯,d[R]都没有变化,

  而变化的只有:d[L]d[L]增加了xx,d[R+1]d[R+1]减少了xx;

  所以只需要add(L,x),add(R+1,-x)即可。

再说查询:

  我们要单点查询a[pos]a[pos],由上可知a[pos]=d[1]+d[2]+⋯+d[pos]a[pos]=d[1]+d[2]+⋯+d[pos],

  那么原来的sum(pos)函数不用修改,就正好能返回a[pos]a[pos]的值。

代码:

复制代码
//BIT - 区间修改,单点查询 - st
struct _BIT{
int N,C[MAXN];
int lowbit(int x){return x&(-x);}
void init(int n)//初始化共有n个点
{
N=n;
for(int i=1;i<=N;i++) C[i]=0;
}
void add(int pos,int val)
{
while(pos<=N) C[pos]+=val,pos+=lowbit(pos);
}
void range_add(int l,int r,int x)//区间[l,r]加x
{
add(l,x);
add(r+1,-x);
}
int ask(int pos)//查询pos点的值
{
int ret=0;
while(pos>0)
{
ret+=C[pos];
pos-=lowbit(pos);
}
return ret;
}
}BIT;
//BIT - 区间修改,单点查询 - ed
复制代码

③区间修改,区间查询BIT:

这个就很骚气了,这样的BIT可以很轻松地应付线段树模板题。

我们看到,由于我们目标记录的是数组a[1:n]a[1:n],而实际存储的是d[1:n]d[1:n],

那么已经实现了区间修改,如何完成区间查询呢?显然,区间查询的基础是快速求数组a[1:n]a[1:n]的前缀和,

显然数组a[1:n]a[1:n]的前缀和:

  a[1]+a[2]+⋯+a[i]=d[1]×i+d[2]×(i−1)+⋯+d[i]×1a[1]+a[2]+⋯+a[i]=d[1]×i+d[2]×(i−1)+⋯+d[i]×1
不难发现右侧可以化成:

  d[1]×i+d[2]×(i−1)+⋯+d[i]×1=[d[1]×(i+1)+d[2]×(i+1)+⋯+d[i]×(i+1)]−[d[1]×1+d[2]×2+⋯+d[i]×i]=(i+1)×(d[1]+d[2]+⋯+d[i])−(d[1]×1+d[2]×2+⋯+d[i]×i)d[1]×i+d[2]×(i−1)+⋯+d[i]×1=[d[1]×(i+1)+d[2]×(i+1)+⋯+d[i]×(i+1)]−[d[1]×1+d[2]×2+⋯+d[i]×i]=(i+1)×(d[1]+d[2]+⋯+d[i])−(d[1]×1+d[2]×2+⋯+d[i]×i)
这样一来,我们就可以想到,在原来的数组C[1:n]C[1:n]记录C[i]=d[i]C[i]=d[i]的基础上,

再搞一个数组C2[1:n]C2[1:n]记录C2[1:n]=d[i]×iC2[1:n]=d[i]×i即可。

代码:

复制代码
//BIT - 区间修改,区间查询 - st
struct _BIT{
int N;
ll C[MAXN],C2[MAXN];//分别记录d[i]和d[i]i
int lowbit(int x){return x&(-x);}
void init(int n)//初始化共有n个点
{
N=n;
memset(C,0,sizeof(C));
memset(C2,0,sizeof(C2));
}
void add(int pos,ll val)
{
for(int i=pos;i<=N;i+=lowbit(i)) C[i]+=val,C2[i]+=val
pos;
}
void range_add(int l,int r,ll x)//区间[l,r]加x
{
add(l,x);
add(r+1,-x);
}
ll ask(int pos)//查询pos点的值
{
ll ret=0;
for(int i=pos;i>0;i-=lowbit(i)) ret+=(pos+1)*C[i]-C2[i];
return ret;
}
ll range_ask(int l,int r)
{
return ask(r)-ask(l-1);
}
}BIT;
//BIT - 区间修改,区间查询 - ed

猜你喜欢

转载自blog.51cto.com/13887363/2150105