1. 程式人生 > >樹狀數組進階 - 區間修改區間查詢OA信用盤平臺出租、二維樹狀數組

樹狀數組進階 - 區間修改區間查詢OA信用盤平臺出租、二維樹狀數組

沒有 信用 turn forum 修改 平臺出租 數字 現在 簡單

①首先是最基礎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];

  若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

樹狀數組進階 - 區間修改區間查詢OA信用盤平臺出租、二維樹狀數組