Pada Chapter hashing/array dasar, kita membangun Fenwick tree (Binary Indexed Tree) untuk prefix sum dengan point update. Sekarang kita melangkah lebih jauh: 2D Fenwick tree untuk prefix sum matriks, dan range-update + range-query dengan dua BIT yang saling melengkapi. Fenwick unggul dari segment tree dalam hal konstanta kecil, memori O(n) (vs 4n), dan kode yang sangat ringkas. Untuk operasi yang ** hanya** butuh prefix query dan point update, Fenwick adalah pilihan terbaik. Untuk range update + range query, dua BIT trik menghasilkan O(log n) tanpa kompleksitas lazy propagation.
Aturan: Operasi yang Didukung Fenwick #
Fenwick tree asli mendukung:
add(idx, val): tambahvalkearr[idx], O(log n).prefix(idx): jumlaharr[0..idx], O(log n).range(l, r) = prefix(r) - prefix(l-1), O(log n).
Dibutuhkan hanya prefix-query-friendly operations (operator asosiatif + identitas, sama seperti segment tree). Fenwick tidak mendukung min/max dengan range update secara elegan — segment tree lazy diperlukan untuk itu.
Mekanisme: LSB dan Tree Implicit #
Fenwick memanfaatkan representasi biner indeks. Misal idx = 12 = 0b1100. LSB (least significant bit) = 0b0100 = 4. idx - LSB = 8. Artinya prefix(12) mencakup range [8, 11] (4 elemen) plus prefix(7). Secara rekursif, prefix(12) = bit_[12] + prefix(8 - 1) = bit_[12] + bit_[8] + prefix(7). Setiap bit_[idx] menyimpan sum dari arr[idx - LSB + 1 .. idx].
Operasi add dan prefix keduanya melibatkan rantai LSB yang panjangnya O(log n).
Implementasi Fenwick 1D, Range-Update, dan 2D #
#include <iostream>
#include <vector>
class Fenwick {
public:
explicit Fenwick(int n) : n_(n), bit_(n + 1, 0) {}
void add(int idx, long long val) {
for (++idx; idx <= n_; idx += idx & (-idx)) bit_[idx] += val;
}
long long prefix(int idx) const {
long long s = 0;
for (++idx; idx > 0; idx -= idx & (-idx)) s += bit_[idx];
return s;
}
long long range(int l, int r) const { return prefix(r) - prefix(l - 1); }
private:
int n_;
std::vector<long long> bit_;
};
// Range update + range query via two BITs (all 1-based internally)
class RangeFenwick {
public:
explicit RangeFenwick(int n) : n_(n), b1_(n + 2, 0), b2_(n + 2, 0) {}
// l, r are 0-based; convert to 1-based internally
void range_add(int l, int r, long long val) {
++l; ++r;
add_internal(b1_, l, val);
add_internal(b1_, r + 1, -val);
add_internal(b2_, l, val * (l - 1));
add_internal(b2_, r + 1, -val * r);
}
long long prefix_sum(int idx) const {
++idx;
return sum_internal(b1_, idx) * idx - sum_internal(b2_, idx);
}
long long range_sum(int l, int r) const {
return prefix_sum(r) - prefix_sum(l - 1);
}
private:
int n_;
std::vector<long long> b1_, b2_;
static void add_internal(std::vector<long long>& bit, int idx, long long val) {
for (; idx < (int)bit.size(); idx += idx & (-idx)) bit[idx] += val;
}
static long long sum_internal(const std::vector<long long>& bit, int idx) {
long long s = 0;
for (; idx > 0; idx -= idx & (-idx)) s += bit[idx];
return s;
}
};
class Fenwick2D {
public:
Fenwick2D(int r, int c) : R_(r), C_(c), bit_(r + 1, std::vector<long long>(c + 1, 0)) {}
void add(int r, int c, long long val) {
for (int i = r + 1; i <= R_; i += i & (-i))
for (int j = c + 1; j <= C_; j += j & (-j))
bit_[i][j] += val;
}
long long prefix(int r, int c) const {
long long s = 0;
for (int i = r + 1; i > 0; i -= i & (-i))
for (int j = c + 1; j > 0; j -= j & (-j))
s += bit_[i][j];
return s;
}
long long range(int r1, int c1, int r2, int c2) const {
return prefix(r2, c2) - prefix(r1 - 1, c2) - prefix(r2, c1 - 1) + prefix(r1 - 1, c1 - 1);
}
private:
int R_, C_;
std::vector<std::vector<long long>> bit_;
};
int main() {
Fenwick f(8);
int vals[] = {3, 1, 4, 1, 5, 9, 2, 6};
for (int i = 0; i < 8; ++i) f.add(i, vals[i]);
std::cout << "Fenwick 1D prefix[5] = " << f.prefix(5) << '\n';
std::cout << "Fenwick 1D range[2,5] = " << f.range(2, 5) << '\n';
RangeFenwick rf(8);
rf.range_add(2, 5, 10);
std::cout << "\nRangeFenwick setelah add(2,5,10):\n";
std::cout << " range[2,5] = " << rf.range_sum(2, 5) << '\n';
std::cout << " range[0,7] = " << rf.range_sum(0, 7) << '\n';
rf.range_add(0, 3, 100);
std::cout << "setelah add(0,3,100):\n";
std::cout << " range[0,7] = " << rf.range_sum(0, 7) << '\n';
std::cout << " range[4,5] = " << rf.range_sum(4, 5) << '\n';
Fenwick2D f2(3, 3);
f2.add(0, 0, 1); f2.add(0, 1, 2); f2.add(0, 2, 3);
f2.add(1, 0, 4); f2.add(1, 1, 5); f2.add(1, 2, 6);
f2.add(2, 0, 7); f2.add(2, 1, 8); f2.add(2, 2, 9);
std::cout << "\nFenwick 2D prefix[1,1] = " << f2.prefix(1, 1) << '\n';
std::cout << "Fenwick 2D range[0,0]-[2,2] = " << f2.range(0, 0, 2, 2) << '\n';
std::cout << "Fenwick 2D range[1,1]-[2,2] = " << f2.range(1, 1, 2, 2) << '\n';
return 0;
}Output yang Dihasilkan:
Fenwick 1D prefix[5] = 23
Fenwick 1D range[2,5] = 19
RangeFenwick setelah add(2,5,10):
range[2,5] = 40
range[0,7] = 40
setelah add(0,3,100):
range[0,7] = 440
range[4,5] = 20
Fenwick 2D prefix[1,1] = 12
Fenwick 2D range[0,0]-[2,2] = 45
Fenwick 2D range[1,1]-[2,2] = 28Bedah Kode Baris-per-Baris #
for (++idx; idx <= n_; idx += idx & (-idx))padaadd: konversi 0-based ke 1-based (++idx). LSB didapat denganidx & (-idx)— ini trik bitwise standar. Iterasi menaik sambil menambahkanvalke setiap node yang cover idx. Contoh:add(0, val)menambahbit_[1],bit_[2],bit_[4],bit_[8], dst. — semua node yang cover index 0.for (++idx; idx > 0; idx -= idx & (-idx))padaprefix: iterasi menurun sambil menjumlahkan node-node yang membentuk prefix. Contoh:prefix(12)menjumlahkanbit_[12],bit_[8],bit_[0](tidak ada). Tiga iterasi = O(log n).range(l, r) = prefix(r) - prefix(l-1): trik standar prefix-sum untuk dapat range sum.RangeFenwickdengan dua BIT: trik untuk range-update + range-query. Setelahrange_add(l, r, val), prefix sum baruprefix(idx)untukidx ∈ [l, r]naik sebesarval * (idx - l + 1) = val * idx - val * (l - 1). Untukidx > r, naikval * (r - l + 1)konstan. Dua BITb1_(koefisienval * idx) danb2_(intercept-val * (l-1)atau-val * r) memodelkan fungsi piecewise-linear ini.add_internal(b1_, l, val); add_internal(b1_, r+1, -val): membentuk fungsi step —b1bernilaivaldi[l, r], 0 di tempat lain. Multiply denganidxsaat query →val * (idx - l + 1)untukidx ∈ [l, r].add_internal(b2_, l, val * (l-1)); add_internal(b2_, r+1, -val * r): koreksi intercept. Formula akhirprefix(idx) = sum_internal(b1, idx) * idx - sum_internal(b2, idx).Fenwick2D: extend ke 2D dengan nested loop. Setiap nodebit_[i][j]menyimpan sum dari rectangle[i - LSB(i) + 1, j - LSB(j) + 1]hingga[i, j]. Kompleksitas O(log R × log C) per operasi.range(r1, c1, r2, c2): inclusion-exclusion standar 2D prefix sum.- Verifikasi:
- 1D:
prefix[5] = 3+1+4+1+5+9 = 23,range[2,5] = 4+1+5+9 = 19. ✓ - RangeFenwick: setelah
add(2,5,10), arr = [0,0,10,10,10,10,0,0].range[2,5] = 40,range[0,7] = 40. Setelahadd(0,3,100), arr = [100,100,110,110,10,10,0,0].range[0,7] = 440,range[4,5] = 20. ✓ - 2D: matriks
[[1,2,3],[4,5,6],[7,8,9]].prefix[1,1] = 1+2+4+5 = 12.range[0,0]-[2,2] = 45.range[1,1]-[2,2] = 5+6+8+9 = 28. ✓
- 1D:
Kompleksitas dan Perbandingan #
| Struktur | Point update | Range query | Range update | Memori |
|---|---|---|---|---|
| Fenwick 1D | O(log n) | O(log n) | — | n+1 |
| RangeFenwick (2 BIT) | — | O(log n) | O(log n) | 2n |
| Fenwick 2D | O(log R·log C) | O(log R·log C) | — | R×C |
| Segment tree lazy | O(log n) | O(log n) | O(log n) | 8n |
Ringkasan #
- Fenwick tree memakai representasi biner index dan operasi LSB
idx & (-idx). - 1D:
adddanprefixkeduanya O(log n), memori O(n). - RangeFenwick dengan dua BIT mendukung range-update + range-query untuk sum, O(log n).
- Fenwick 2D: nested BIT untuk prefix sum matriks, O(log R · log C) per operasi.
- Selalu 1-based internal.
idx = 0adalah infinite loop jebakan. - Fenwick unggul untuk prefix-query-friendly operations (sum, XOR). Untuk min/max dengan update, segment tree lebih fleksibel.
- Trade-off vs segment tree: Fenwick kode lebih pendek, konstanta ~2× lebih kecil, memori setengah. Segment tree lebih fleksibel untuk operasi kompleks (lazy, persistent, merge-sort).