Binary Indexed Tree の基礎と応用

概要

Binary Indexed Tree(BIT)は、単点更新と区間クエリを効率的に処理できるデータ構造です。計算量 O(log N) で操作を実行可能であり、競技プログラミングやアルゴリズム最適化で広く利用されます。

構造と原理

BIT は 2 進数表現に基づく構造を利用します。任意の整数は複数の 2 のべき乗和で表せることから、配列要素をべき乗区間で管理します。lowbit 演算(x & -x で得られる最下位ビットの値)がキーポイントです。

基本操作

  • 単点更新: 指定要素に値を加算
  • 接頭辞和クエリ: 先頭から指定位置までの和を取得

コード例


class BIT {
private:
    vector<int> tree;
    int size;

    int lowbit(int x) { return x & -x; }

public:
    BIT(int n) : size(n), tree(n + 1) {}

    // 単点更新
    void update(int idx, int delta) {
        while (idx <= size) {
            tree[idx] += delta;
            idx += lowbit(idx);
        }
    }

    // 接頭辞和取得
    int query(int idx) {
        int result = 0;
        while (idx > 0) {
            result += tree[idx];
            idx -= lowbit(idx);
        }
        return result;
    }

    // 区間和取得
    int range_query(int l, int r) {
        return query(r) - query(l - 1);
    }
};

応用技法

逆数カウント

値域 BIT を用いて配列要素の出現回数を管理。要素 a[i] に対して、現在の BIT 内で a[i] より大きい値の出現数をクエリで取得。


int count_inversions(vector<int>& arr) {
    int n = arr.size();
    BIT bit(n);
    int ans = 0;
    for (int i = 0; i < n; i++) {
        ans += bit.query(n) - bit.query(arr[i]);
        bit.update(arr[i], 1);
    }
    return ans;
}

区間最大値クエリ

通常はセグメント木が使われますが、BIT でも log²N 計算量で実装可能。


class RangeMaxBIT {
private:
    vector<int> max_tree;
    vector<int> data;

    int lowbit(int x) { return x & -x; }

public:
    void update(int idx, int value) {
        data[idx] = value;
        int i = idx;
        while (i <= size) {
            max_tree[i] = max(data[i], max_tree[i]);
            i += lowbit(i);
        }
    }

    int query(int l, int r) {
        int result = INT_MIN;
        while (r >= l) {
            result = max(data[r], result);
            r--;
            while (r - lowbit(r) >= l) {
                result = max(max_tree[r], result);
                r -= lowbit(r);
            }
        }
        return result;
    }
};

差分配列管理

区間更新を単点クエリで処理する方法。差分配列 d[i] を BIT で管理。


// 区間 [l, r] への加算
bit.update(l, delta);
bit.update(r + 1, -delta);

// 単点クエリで元配列値取得
int value = bit.query(x);

応用例題

中央値クエリ

BIT と二分探索を組み合わせて動的中央値を取得。座圧(座標圧縮)が必要。


int find_median(BIT& bit, int max_val, int count) {
    int low = 1, high = max_val;
    while (low < high) {
        int mid = (low + high) / 2;
        if (bit.query(mid) > count / 2)
            high = mid;
        else
            low = mid + 1;
    }
    return low;
}

音声処理問題

スライド窓での最大値・最小値取得。BIT と K 番目クエリの組み合わせ。


int kth_smallest(BIT& bit, int k) {
    int result = 0, cnt = 0;
    for (int i = 20; i >= 0; i--) {
        int next = result + (1 << i);
        if (next > max_val) continue;
        if (cnt + bit.query(next) < k) {
            result = next;
            cnt += bit.query(next);
        }
    }
    return result + 1;
}

まとめ

BIT は柔軟性の高いデータ構造で、区間和計算、逆数カウント、動的計画法の高速化など幅広い応用が可能です。適切な変換を施すことで、最大値クエリや多次元拡張も実現できます。

タグ: Binary Indexed Tree データ構造 競技プログラミング アルゴリズム最適化

6月25日 18:51 投稿