樹狀陣列在區間求和問題上有大用,其三種複雜度都比線段樹要低很多……有關區間求和的問題主要有以下三個模型(以下設a[1..n]為乙個長為n的序列,初始值為全0):
(1)「改點求段」型,即對於序列a有以下操作:
修改操作:將a[x]的值加上c;
求和操作:求此時a[l..r]的和。
這是最容易的模型,不需要任何輔助陣列。樹狀陣列中從x開始不斷減lowbit(x)(即x&(-x))可以得到整個[1..x]的和,而從x開始不斷加lowbit(x)則可以得到x的所有前趨。**:
void
add(
intx,
intc)int
sum(
intx)
操作:add(x, c);
操作:sum(r)-sum(l-1)。
(2)「改段求點」型,即對於序列a有以下操作:
修改操作:將a[l..r]之間的全部元素值加上c;
求和操作:求此時a[x]的值。
這個模型中需要設定乙個輔助陣列b:b[i]表示a[1..i]到目前為止共被整體加了多少(或者可以說成,到目前為止的所有add(i, c)操作中c的總和)。
則可以發現,對於之前的所有add(x, c)操作,當且僅當x>=i時,該操作會對a[i]的值造成影響(將a[i]加上c),又由於初始a[i]=0,所以有a[i] = b[i..n]之和!而add(i, c)(將a[1..i]整體加上c),將b[i]加上c即可——只要對b陣列進行操作就行了。
【首先對於每個數a定義集合up(a)表示 定義集合down(a)表示。可以發現對於任何a
翻轉乙個區間[a,b](為了便於討論先把原問題降為一維的情況),我們可以把down(b)的所有元素的翻轉次數+1,再把down(a-1)的所有元素的翻轉次數-1。而每次查詢乙個元素c時,只需要統計up(c)的所有元素的翻轉次數之和,即為c實際被翻轉的次數】
這樣就把該模型轉化成了「改點求點」型,只是有一點不同的是,sum(x)不是求b[1..x]的和而是求b[x..n]的和,此時只需把add和sum中的增減次序對調即可(模型1中是add加sum減,這裡是add減sum加)。**:
void
add(
intx,
intc)int
sum(
intx)
操作:add(l-1, -c); add(r, c);
操作:sum(x)。
(3)「改段求段」型,即對於序列a有以下操作:
修改操作:將a[l..r]之間的全部元素值加上c;
求和操作:求此時a[l..r]的和。
這是最複雜的模型,需要兩個輔助陣列:b[i]表示a[1..i]到目前為止共被整體加了多少(和模型2中的一樣),c[i]表示a[1..i]到目前為止共被整體加了多少的總和(或者說,c[i]=b[i]*i)。
對於add(x, c),只要將b[x]加上c,同時c[x]加上c*x即可(根據c[x]和b[x]間的關係可得);
而add(x, c)操作是這樣影響a[1..i]的和的:若x=i)會將a[1..i]的和加上i*c。也就是,a[1..i]之和 = b[i..n]之和 * i + c[1..i-1]之和。
這樣對於b和c兩個陣列而言就變成了「改點求段」(不過b是求字尾和而c是求字首和)。
另外,該模型中需要特別注意越界問題,即x=0時不能執行sum_b操作和add_c操作!**:
void
add_b(
intx,
intc)void
add_c(
intx,
intc)int
sum_b(
intx)
intsum_c(
intx)
inline
intsum(
intx)
操作:
add_b(r, c); add_c(r, c);
if (l > 1)
操作:sum(r) - sum(l - 1)。
樹狀陣列區間求和三種模型
樹狀陣列在區間求和問題上有大用,其三種複雜度都比線段樹要低很多 有關區間求和的問題主要有以下三個模型 以下設a 1.n 為乙個長為n的序列,初始值為全0 1 改點求段 型,即對於序列a有以下操作 修改操作 將a x 的值加上c 求和操作 求此時a l.r 的和。這是最容易的模型,不需要任何輔助陣列。...
詳解樹狀陣列三種模型
首先說明下 最後的最大值模型的 沒有測試,不過應該是沒問題的。其它三個更新求和的模型的 借鑑於網上的各個博文,應該是沒問題的。其中前兩個模型的 已測試無誤。樹狀陣列與線段樹在思想上很類似的一種資料結構,它比線段樹更簡潔,但它的適用範圍也小了些。提供一篇博文,詳解樹狀陣列的 樹狀陣列是乙個可以高效的進...
樹狀陣列 區間求和
樹狀陣列 是乙個查詢和修改複雜度都為log n 的資料結構,假設陣列a 1.n 那麼查詢a 1 a n 的時間是 log n 級別的。所以如果要解決 陣列中的元素不斷被修改,怎麼才能快速地獲取陣列中連續m個數的和 這個問題的話,用樹狀陣列就再好不過了 首先,什麼是樹狀陣列呢?樹狀陣列就是用另外乙個陣...