Fenwick stablo (BIT)

Lekcija 10 od 11 · Strukture podataka

Fenwick stablo (Binary Indexed Tree) održava niz i odgovara na pitanje „koliki je zbir prvih k elemenata?“ – i to dok se elementi menjaju. Obe operacije traju O(log n), a kod je kratak: desetak redova.

Pre nego što počneš: potrebno predznanje – Strukture podataka – vodič.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.

Problem: zbirovi uz izmene

Dat je niz od n brojeva i q upita dve vrste: promeni element na poziciji i i koliki je zbir na intervalu [l, r]. Običan niz daje izmenu u O(1), a zbir u O(n). Prefiksni zbirovi daju zbir u O(1), ali izmenu u O(n). Za n, q do 2·105 oba pristupa su prespora, a Fenwick stablo radi oba upita u O(log n).

Ideja: lowbit

Pozicije su od 1 do n. Element bit[i] čuva zbir dela niza koji se završava na i, a dužina tog dela je najniži bit broja i: lowbit(i) = i & (-i). Na primer, bit[12] čuva zbir pozicija 9–12 (12 = 1100₂, najniži bit je 4), a bit[7] samo poziciju 7.

  • Zbir prvih i: saberi bit[i], pa pređi na i -= lowbit(i), dok i > 0.
  • Dodaj x na poziciju i: dodaj x u bit[i], pa pređi na i += lowbit(i), dok i ≤ n.

U oba slučaja broj koraka je najviše broj bitova, tj. O(log n).

#include <iostream>
#include <vector>
using namespace std;

int n;
vector<long long> bit;

void dodaj(int i, long long x) {          // a[i] += x
    for (; i <= n; i += i & (-i)) bit[i] += x;
}

long long prefiks(int i) {                // a[1] + ... + a[i]
    long long s = 0;
    for (; i > 0; i -= i & (-i)) s += bit[i];
    return s;
}

int main() {
    int q;
    cin >> n >> q;
    bit.assign(n + 1, 0);
    vector<long long> a(n + 1);
    for (int i = 1; i <= n; i++) { cin >> a[i]; dodaj(i, a[i]); }
    while (q--) {
        int tip;
        cin >> tip;
        if (tip == 1) {                   // 1 i v : a[i] = v
            int i; long long v;
            cin >> i >> v;
            dodaj(i, v - a[i]);           // dodajemo razliku
            a[i] = v;
        } else {                          // 2 l r : zbir a[l..r]
            int l, r;
            cin >> l >> r;
            cout << prefiks(r) - prefiks(l - 1) << "\n";
        }
    }
    return 0;
}

Izmena „postavi na v“ se pretvara u „dodaj razliku v − a[i]“, pa zato čuvamo i sam niz a.

Kada Fenwick, a kada segmentno stablo?

Fenwick je kraći i brži, ali prirodno radi samo za operacije koje imaju „suprotnu“ (zbir – oduzimanje, XOR). Za minimum, maksimum ili složenije upite na intervalu koristi se segmentno stablo.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: Teže

Broj inverzija

Dat je niz od n različitih brojeva od 1 do n (permutacija, n ≤ 2·105). Inverzija je par i < j za koji je a[i] > a[j]. Koliko ima inverzija?

Primer

Ulaz

5
2 4 1 3 5

Izlaz

3
ZADATAK 02Težina: Srednje teško

Prodaja po danima

Prodavnica beleži prodaju za n dana (u početku 0). Upiti: P d k – dana d prodato je još k komada; Z a b – koliko je ukupno prodato od dana a do dana b?

Primer

Ulaz

5 4
P 2 3
P 4 5
Z 1 3
Z 2 5

Izlaz

3
8
Česte greške i saveti
  • Fenwick koristi pozicije od 1; za pozicije od 0 dodaj 1 pri svakom pozivu.
  • Zbir na intervalu [l, r] je prefiks(r) − prefiks(l − 1).
  • „Postavi vrednost“ pretvori u „dodaj razliku“ i čuvaj trenutni niz.
  • Zbirovi lako prekorače int – koristi long long.

Proveri znanje

Otvori kviz

Mini kviz: Fenwick stablo

1. Koliko je 12 & (-12)?




2. Kolika je složenost upita za zbir prvih k elemenata?




3. Koji upit Fenwick ne podržava direktno?