差分
このページの2つのバージョン間の差分を表示します。
両方とも前のリビジョン前のリビジョン次のリビジョン | 前のリビジョン次のリビジョン両方とも次のリビジョン | ||
programming_algorithm:data_structure:binary_indexed_tree [2019/11/01] – ikatakos | programming_algorithm:data_structure:binary_indexed_tree [2020/12/13] – [より柔軟な実装] ikatakos | ||
---|---|---|---|
行 1: | 行 1: | ||
- | ======Binary Indexed Tree====== | + | ======Binary Indexed Tree(Fenwick Tree)====== |
+ | |||
+ | =====概要===== | ||
[[http:// | [[http:// | ||
- | Range Sum Query(区間の和)に対する効率的なデータ構造 | + | Range Sum Query(区間の和)に対する効率的なデータ構造。 |
- | * 数列$a_1, | + | * 数列 $a_1, |
* 以下の2つの要求を処理する | * 以下の2つの要求を処理する | ||
- | * $a_i$に$x$を加算($1 \le i \le n$) | + | * $a_i$ に $x$ を加算($1 \le i \le N$) |
- | * $a_s$から$a_t$までの合計を得る($1 \le s \le t \le n$) | + | * $a_s~a_t$ の合計を得る($1 \le s \le t \le N$) |
Binary Indexed Treeは、この2つを高速に行える。 | Binary Indexed Treeは、この2つを高速に行える。 | ||
これだけ見ると「そんなのが何の役に立つの?」って感じだが、頭のいい人だと全然関係ないように見える問題を変換して効率的に解いたりしちゃうらしい。 | これだけ見ると「そんなのが何の役に立つの?」って感じだが、頭のいい人だと全然関係ないように見える問題を変換して効率的に解いたりしちゃうらしい。 | ||
+ | |||
+ | =====実装===== | ||
詳しい説明は上のpdfで懇切丁寧に行われているので、Pythonコードだけメモ。 | 詳しい説明は上のpdfで懇切丁寧に行われているので、Pythonコードだけメモ。 | ||
行 45: | 行 49: | ||
</ | </ | ||
- | 注意点として、配列のindex等と異なり、添え字は1から始まる。そうしないと効率的に上下の要素を特定できないため。 | + | 注意点として、配列のindex等と異なり、添え字は1から始まる。その方が効率的に上下の要素を特定できるため。 |
- | | + | ⇤←←←←←←8 |
- | 4 | + | |
- | 2 6 | + | ⇤2 ⇤6 ⇤10 |
- | 1 3 5 7 9 ... | + | 1 3 5 7 9 |
+ | |||
+ | 添え字を2進数にしたもの。 | ||
+ | |||
+ | ⇤─←─←─←─←─←─←─1000 | ||
+ | ⇤─←─←─0100 | ||
+ | ⇤─0010 | ||
+ | 0001 0011 0101 0111 1001 ... | ||
+ | |||
+ | 「$a_1 ~ a_7$ の和」を求めるときに足される箇所。7を表す " | ||
+ | |||
+ | ⇤─←─←─←─←─←─←─1000 | ||
+ | ⇤─←─←【0100】 | ||
+ | ⇤─0010 | ||
+ | 0001 0011 0101 【0111】 | ||
+ | |||
+ | |||
+ | ==== より柔軟な実装 ==== | ||
+ | |||
+ | Fenwick木に載せられるのは足し算だけでなく、XORやかけ算、また(わりと致命的な制約はあるが一応は)minやmaxも載せられる。 | ||
+ | |||
+ | 「初期値」と「演算する関数」さえ決めればよいので、これを外部注入できるようにすると、柔軟な実装になる。 | ||
+ | |||
+ | ただし、Python(などインタプリタ言語)では一般的に「'' | ||
+ | 柔軟性のために演算を関数で与える関係上、前者の実装となるので、演算が単純でギリギリまで高速化を行いたい場合には向かない。 | ||
+ | |||
+ | |||
+ | ++++ Python3 | | ||
+ | |||
+ | <sxh python> | ||
+ | class FenwickTreeInjectable: | ||
+ | def __init__(self, | ||
+ | self.size = n | ||
+ | self.tree = [identity_factory() for _ in range(n + 1)] | ||
+ | self.func = func | ||
+ | self.idf = identity_factory | ||
+ | |||
+ | def add(self, i, x): | ||
+ | tree = self.tree | ||
+ | func = self.func | ||
+ | while i <= self.size: | ||
+ | tree[i] = func(tree[i], | ||
+ | i += i & -i | ||
+ | |||
+ | def sum(self, i): | ||
+ | s = self.idf() | ||
+ | tree = self.tree | ||
+ | func = self.func | ||
+ | while i > 0: | ||
+ | s = func(s, tree[i]) | ||
+ | i -= i & -i | ||
+ | return s | ||
+ | |||
+ | def lower_bound(self, | ||
+ | """ | ||
+ | 累積和がx以上になる最小のindexと、その直前までの累積和(未検証) | ||
+ | |||
+ | :param lt: lt(a, b) で a < b ならTrueを返す関数 | ||
+ | """ | ||
+ | total = self.idf() | ||
+ | pos = 0 | ||
+ | tree = self.tree | ||
+ | func = self.func | ||
+ | for i in range(self.depth, | ||
+ | k = pos + (1 << i) | ||
+ | new_total = func(total, tree[k]) | ||
+ | if k <= self.size and lt(new_total, | ||
+ | total = new_total | ||
+ | pos += 1 << i | ||
+ | return pos + 1, total | ||
+ | </ | ||
+ | |||
+ | listやdictなどのオブジェクトを載せることもできる。\\ | ||
+ | その場合の注意点として、Pythonでは、オブジェクトを特に工夫無くコピーするとインスタンス自体が同じとなる。つまりどれか1つへの反映が他の全てに反映されてしまう。 | ||
+ | |||
+ | それを防ぐため、'' | ||
+ | |||
+ | <sxh python; | ||
+ | |||
+ | a = [0, 0] | ||
+ | aaa = [a] * 3 | ||
+ | print(aaa) | ||
+ | |||
+ | aaa[0][0] = 5 | ||
+ | print(aaa) | ||
+ | |||
+ | |||
+ | # 以下の実装だと、上のようなことが発生する | ||
+ | class FenwickTreeInjectable: | ||
+ | def __init__(self, | ||
+ | self.size = n | ||
+ | self.tree = [identity_element] * (n + 1) # ←ここがまずい | ||
+ | self.func = func | ||
+ | |||
+ | ...略 | ||
+ | |||
+ | </ | ||
+ | |||
+ | ++++ | ||
=====区間に対する更新===== | =====区間に対する更新===== | ||
- | 上記のBit.add()は、点に対する更新しか行えない。では「a3~a7に一律に5を加算」などが必要な場合にどうするか。 | + | 上記のBit.add()は、点に対する更新しか行えない。では「$a_3~a_7$ に一律に5を加算」などが必要な場合にどうするか。 |
====区間の和が必要ない場合==== | ====区間の和が必要ない場合==== | ||
行 61: | 行 163: | ||
区間の和は必要なく、ある指定した位置の値だけわかれば良い場合。つまり、 | 区間の和は必要なく、ある指定した位置の値だけわかれば良い場合。つまり、 | ||
- | * $a_s$から$a_t$までに一律$x$を加算($1 \le s \le t \le n$) | + | * $a_s$ から $a_t$ までに一律 $x$ を加算($1 \le s \le t \le n$) |
- | * $a_i$の値を得る($1 \le i \le n$) | + | * $a_i$ の値を得る($1 \le i \le n$) |
この場合は、差分に着目することで、BITをそのまま利用できる。(上記pdf参照) | この場合は、差分に着目することで、BITをそのまま利用できる。(上記pdf参照) | ||
- | * $s$~$t$に一律$x$を加算→「$s$に$x$、$t+1$に$-x$を加算」 | + | * $a_s~a_t$ に一律 $x$ を加算→「$s$ に $x$、$t+1$ に $-x$ を加算」 |
- | * 位置$i$の値を取得→「$1$~$i$の合計を取得」 | + | * $a_i$ の値を取得→「$1~i$ の合計を取得」 |
- | なお、見ての通り添え字が$n+1$まで参照される可能性があるので、便宜上、Bitのサイズは1大きい値で作っておく | + | なお、見ての通り添え字が $n+1$ まで参照される可能性があるので、便宜上、BITのサイズは1大きい値で作っておく |
====区間の和も欲しい場合==== | ====区間の和も欲しい場合==== | ||
- | * $a_s$から$a_t$までに一律$x$を加算($1 \le s \le t \le n$) | + | * $a_s~a_t$ に一律 $x$ を加算($1 \le s \le t \le n$) |
- | * $a_s$から$a_t$までの合計を得る($1 \le s \le t \le n$) | + | * $a_s~a_t$ の合計を得る($1 \le s \le t \le n$) |
< | < | ||
行 132: | 行 234: | ||
* [[https:// | * [[https:// | ||
+ | =====累積和の二分探索===== | ||
+ | 二分探索により、累積和が $x$ のindexや、$x$ を越えない最大のindexとその時の累積和などを得ることができる。 | ||
+ | * [[https:// | ||
+ | (※以下、sum, | ||
+ | <sxh python> | ||
+ | class Bit: | ||
+ | def __init__(self, | ||
+ | self.size = n | ||
+ | self.tree = [0] * (n + 1) | ||
+ | self.depth = n.bit_length() | ||
+ | |||
+ | def sum(self, i): | ||
+ | s = 0 | ||
+ | while i > 0: | ||
+ | s += self.tree[i] | ||
+ | i -= i & -i | ||
+ | return s | ||
+ | |||
+ | def add(self, i, x): | ||
+ | while i <= self.size: | ||
+ | self.tree[i] += x | ||
+ | i += i & -i | ||
+ | |||
+ | def lower_bound(self, | ||
+ | """ | ||
+ | sum_ = 0 | ||
+ | pos = 0 | ||
+ | for i in range(self.depth, | ||
+ | k = pos + (1 << i) | ||
+ | if k <= self.size and sum_ + self.tree[k] < x: | ||
+ | sum_ += self.tree[k] | ||
+ | pos += 1 << i | ||
+ | return pos + 1, sum_ | ||
+ | |||
+ | </ | ||