4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

区間加算Binary Indexed Tree

Posted at

蟻本3-3「さまざまなデータ構造を操ろう」の区間加算可能なBinary Indexed Treeを行間埋めしました.

問題

数列$A_1,A_2,...,A_N$と$Q$個のクエリが与えられます.クエリを順次処理してください.クエリは次の2種類です.

  • $l,r,x$が与えられるので,区間$A_l,A_{l+1},...,A_r$に$x$を加える.
  • $l,r$が与えられるので,区間$A_l,A_{l+1},...,A_r$の和を求める.

解法

2つのBinary Indexed Tree(BIT)を用意すると高速に処理することができます.

Binary Indexed Treeとは

以下の記事がわかりやすかったです

BITの特徴をまとめると,

  • 1要素$A_i$に$x$を加える操作は高速($O(\log n)$)
  • 区間$A_1,A_2,...,A_r$の和算出も高速($O(\log n)$)
    • 区間$[l,r]$の和は$\sum_{k=1}^{r}a_k - \sum_{k=1}^{l-1}a_k$で
  • ただし,区間$A_l,A_{l+1},...,A_r$に$x$を加える操作は苦手
    • BITを2つ組み合わせると高速に処理できる($O(\log n)$)

”区間に値を加える”とは

準備

数列$A_1,A_2,...,A_N$の区間$[l,r]$に$x$を加算した数列を,$A'_1,A'_2,...,A'_N$と書きます.つまり

A'_i = 
\begin{cases}
A_i+x & \text{if $ l \le i \lt r+1 $,} \\
A_i & \text{otherwise}.
\end{cases}

また,$s(i),s'(i)$を以下のように定義します.

  • $s(i)=\sum_{k=1}^{i}A_k$ (加算前の和)
  • $s'(i)=\sum_{k=1}^{i}A'_k$ (加算後の和)

すると,$s(i)$と$s'(i)$の関係は

  • $1 \le i \lt l$のとき
    $$ s'(i)=s(i) $$
  • $l \le i \lt r+1$のとき
    $$ s'(i)=s(i)+x(i-l+1) $$
  • $r+1 \le i \le N$のとき
    $$ s'(i)=s(i)+x(r-l+1) $$

となります.$s(i)$を左辺に移項するとこのようなグラフになります.

1.png

このグラフのような$s(i)\rightarrow s'(i)$の更新を高速に行うことが目標です.

2つのBITで和を表す

2つのBIT(BIT0とBIT1とします)を用意して,次のように値を管理するとうまくいきます.

$s(i)$ =(BIT0の1~i要素の和)+ $i$ ×(BIT1の1~i要素の和)

BITは内部で数列の情報を保持し,1要素加算と区間和取得を高速に行うことができるデータ構造でしたので,BIT0,BIT1もそれぞれ内部で数列の情報を保持しています.
BIT0が保持している数列を$a_1,a_2,...,a_N$とします.すなわち

(BIT0の1~i要素の和)= $\sum_{k=1}^{i}a_k$

です.同様にBIT1が保持している数列を数列$b_1,b_2,...,b_N$とおくと,$s(i)$は次のように書けます.

s(i)= \sum_{k=1}^{i}a_k + i \sum_{k=1}^{i}b_k

$s(i)$を更新して$s'(i)$とした後の,BIT0とBIT1の保持している数列をそれぞれ$a'_i,b'_i$と書くと,

s'(i)= \sum_{k=1}^{i}a'_k + i \sum_{k=1}^{i}b'_k

なので

s'(i)-s(i)= \sum_{k=1}^{i}(a'_k-a_k) + i \sum_{k=1}^{i}(b'_k-b_k) 

と書けます.

2つのBITの更新方法

「$s'(i)-s(i)$」を2つのBITに割り振ります.

BIT1: $l$から$r$にかけての傾き部分(図の(b))
BIT0: 上記以外(図の(a))

2.png

BIT0

BIT0で管理している数列の和(a)から$\Sigma$を取り除くと(a)'のようになります.

3.png

(a)'の$a'_i-a_i$はBIT0で管理している数列$a_i$の更新前後の差なので,これがそのままBIT0への加算量になります.つまり,

  • BIT0の$l$個目の要素に$-x(l-1)$を加算
  • BIT0の$r+1$個目の要素に$xr$を加算

です.BITの得意な1要素への加算に落とし込むことができました.

BIT1

BIT1の和には$i$が掛けてあったので,まず$i$を取り除いて(b)'とします.さらに$\Sigma$を取り除くと(b)''になります.

4.png

(b)''から,BIT1も次のような1要素への加算に落とし込むことができました.

  • BIT1の$l$個目の要素に$x$を加算
  • BIT1の$r+1$個目の要素に$-x$を加算

BIT0,BOT1の操作をまとめると

区間$A_l,A_{l+1},...,A_r$に$x$を加える操作は,次の4つの「BITを用いた数列の1要素への加算」で表すことができます.

  • BIT0の$l$個目の要素に$-x(l-1)$を加算
  • BIT1の$l$個目の要素に$x$を加算
  • BIT0の$r+1$個目の要素に$xr$を加算
  • BIT1の$r+1$個目の要素に$-x$を加算

和の求め方

数列$A_1,A_2,...,A_N$の$i$要素目までの和は,$s(i)$の定義をそのまま利用して次のように求めます.

$s(i)$ =(BIT0の1~i要素の和)+ $i$ ×(BIT1の1~i要素の和)

実装例

冒頭のリンクの先を参照してください.

終わりに

蟻本は一目見ると説明不足に感じますが,後で読み直すと必要十分な説明になっていますね.

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?