ACM之路 8 樹狀陣列

2021-10-10 03:55:53 字數 3559 閱讀 4928

適用於「單點更新,區間求和」或者「區間更新,單點求和」。

假設有乙個陣列a,有兩種操作,1是更新某一項的值,2是查詢(l, r)區間的值,這是「單點更新,區間求和」。我們先看下面的。

從可以看到,

c[1] = a[1]

c[2] = c[1] + a[2]  = a[1] + a[2]

c[3] = a[3]

c[4] = c[2] + c[3] + a[4] =  a[1] + a[2] +  a[3] + a[4]

有什麼規律嗎?

可以看到,(c[i]代表的是a[i - 2^k + 1] + a[i - 2^k + 2] ..... + a[i]的值),這裡的k代表i二進位制中"最後乙個1後面0的個數「,我們稱2^k為lowbit(i)

什麼是lowbit(i)呢。lowbit(i)表示的是「最後乙個1加」,如果有0加所有的0,如lowbit(5), 5的二進位制是101,最後1位是1,所以lowbit(5)等於1;再如lowbit(6),二進位制是110,所以lowbit(6) = 2.

綜上所述,c[i]代表的值就為a[i - lowbit(i) + 1] + a[i - lowbit(i) + 2] + .... + a[i],如c[4],4的lowbit為100,也即4本身,所以c[4] = a[4 - 4 + 1] + a[4 - 4 + 2] + ... + a[4] = a[1] + a[2] + a[3] + a[4].

然後我們再給出一種求lowbit(i)的建議**:(這是利用計算機補碼的性質實現的)

int lowbit(int x)
由下面的圖可以看到,如果更新a陣列第3個點,也即a[3]的值,如加5,則c[4] ,c[8]也要加5,那加到什麼時候停止呢?假設a陣列有n個值,則加到超過n時停止。

我們給出單點更新的**:(也即更新x點的數值,加上y,則每個包括x點值的c[i]都要更新)

update(int x, int y) 

}

為了方便理解,給出了下面的圖:a[3]更新了,則c[3],c[4],c[8]都要更新。 

我們先想想求a[1] + a[2] + ... + a[n]的情況。如求1~8的和,則c[8]一步就給出了答案,如果求1~6的和,則只求c[4] + c[6]即可。我們先給出求1~x,這x個數的和的**:

ll query(int x) 

return ans;

}

如求1~6的和,則ans += c[6] , 6 - lowbit(6) = 4. ans += c[4]. 4 - lowbit(4) = 0; i ==0 所以推出,因此返回的就是c[4] + c[6]。很簡單吧。

既然已經知道了如何求1~x的值,那麼如果想知道(l,r)區間的值,我們查詢1~r的值減去查詢1~(l - 1)的值,剩下的範圍不就是(l, r)的值了嗎?用**表示也即query(r) - query(l - 1).

我們比較一下計算複雜度,假如有a陣列有n個值,m次提問。則暴力計算區間和的時間複雜度為o(n),總t(n) = o(nm)。如果用了樹狀陣列,用了二進位制的思想,查詢時間很快。相當於是log級別的。t(n) = o(mlogn)

暴力和樹狀陣列的空間複雜度都是o(n).

note:計算lowbit(i)時i不能為0,應該從1開始,因為如果i  = 0 在update和query時會陷入死迴圈。(可以自己試一下效果)

模板題:下面有**參考)

例題:考慮「離散化」->「樹狀陣列」,可以看我的部落格:shuzhuangshuzu )

這裡給出模板題的**,例題**:

#include #include using namespace std;

#define ll long long

ll c[500005];

int n, m, x;

int op, a, b;

//求"最後乙個1"加後面的0

int lowbit(int x)

//單點更新:x節點的值加y

void update(int x, ll y)

}//求1->x的值.若求(l,r)用query(r)-query(l-1)

int query(int x)

return ans;

}int main()

while (m--) else

}return 0;

}

區間更新用到了差分陣列的思想。設陣列a共有n個元素,為a[1], a[2], ...a[n]。令a[0] = 0。

a[1] = a[1] - a[0]

a[2] = a[2] - a[1]

a[3] = a[3] - a[2]

a[n] = a[n] - a[n - 1]

如果要更新(l, r)區間的值,如+ 3,則令a[l] += 3。a[r + 1] -= 3

如更新(1, 3)區間的值,則

a[1] = a[1] - a[0] + 3

a[2] = a[2] - a[1]

a[3] = a[3] - a[2]

a[4] = a[4] - a[3] - 3

則區間更新的**很容易:

//給定l, r, x,(l,r)代表範圍。x代表更新的值

a[l] += x;

a[r + 1] -= x;

我們容易發現下面的結論:由於a[0] = 0

a[1] = a[1]

a[1] + a[2] = a[2]

a[1] + a[2] + a[3] = a[3]

因此,如果要求a[k],也就是求a陣列的前k項和,那麼這裡顯然要用到樹狀陣列了,查詢快。上面的單點更新是不影響a的計算的,不信的話,試一試,發現很神奇。查詢的**就是query(x)的**,與「單點更新,區間求和」的查詢**相同。但是注意查詢的是小a陣列的樹狀陣列,而不是大a陣列的樹狀陣列。

模板題:

例題**:

#include #include using namespace std;

int n, m;

int c[500005];

int lowbit(int x)

void update(int x, int y)

}int query(int x)

return ans;

}int main()

int opt, x, y, k;

while (m--) else

}return 0;

}

假如有乙個陣列a[n][m],也可以建立乙個二維的陣列,需要更新一維的update和query,並把for迴圈轉化為二維。

ACM 模板 樹狀陣列

樹狀陣列作為一種資料結構,很廣泛的運用到關於統計的問題中,樹狀陣列通過類似數的結構每一位都記錄的是從前面到這個點所有的和,要修改起來也很方便。在 上樹狀陣列也很簡單,就是核心的三個函式。const int maxn 1002 一維樹狀陣列 int c maxn int lowbit int x vo...

ACM模板 樹狀陣列

二 應用 三 模板 1 區間求和型別 include include define mem a,b memset a,b,sizeof a define inf 0x3f3f3f3f using namespace std typedef long long ll const int maxn 20...

acm高階之路

一般要做到50行以內的程式不用除錯 100行以內的二分鐘內除錯成功 第一階段 練經典常用演算法,下面的每個演算法給我打上十到二十遍,同時自己精簡 因為太常用,所以要練到寫時不用想,10 15分鐘內打完 1.最短路 floyd dijstra,bellmanford 2.最小生成樹 先寫個prim,k...