DSA sheet / Range queries

Dynamic Range Sum Queries

medium about 35 min Prefix sumsfenwick-treebinary-indexed-treepoint-update
Open on CSES

Do these first: Static Range Sum Queries

The problem in brief

You get an array of n integers (up to 200 000) and q operations (up to 200 000). An operation of type 1 sets the value at position k to u; an operation of type 2 asks for the sum of positions a through b (1-indexed, inclusive). Answer every type 2 operation in order.

Try it

Tab indents; press Esc, then Tab, to leave the box. Ctrl+Enter runs.

Hints

Stuck? Reveal one hint at a time. Each nudges without giving away the next.

The walkthrough

Spoilers ahead: open a section only after you have given the hints a fair try.

How to think

Compare the two extremes. A raw array gives O(1) update and O(n) query; prefix sums give O(1) query and O(n) update. With 2 * 10^5 mixed operations either extreme costs 4 * 10^10. The fix is a balanced compromise where both are O(log n).

The Fenwick tree (binary indexed tree) does it with an array of size n + 1 where tree[i] holds the sum of the last lowbit(i) elements ending at i, with lowbit(i) = i & -i. To get the prefix sum up to i, add tree[i], then move to i - lowbit(i), and repeat until zero: the blocks you visit tile the prefix exactly. To add a delta at position i, add it to tree[i], then move to i + lowbit(i), and repeat until past n: those are exactly the blocks that contain position i.

Because updates here are assignments, keep the current array and add delta = newValue - oldValue. The habit: decide first whether the data changes; if it does, and you need aggregates, reach for a Fenwick or segment tree, and express every change as an additive delta.

Intuition

Think of tree[i] as a bucket that is responsible for a stretch of the array ending at i, with the stretch length set by the trailing zeros of i. Any prefix is covered by at most log n buckets, and any single element sits in at most log n buckets, so touching them all is cheap.

Approach
  1. Build the tree by adding every initial value with the update routine (O(n log n)) and keep the values array a[].
  2. For a type 1 operation (k, u): delta = u - a[k]; a[k] = u; update(k, delta).
  3. For a type 2 operation (a, b): print query(b) - query(a - 1).
  4. query(i) sums tree[i], then i -= i & -i, until i = 0. update(i, d) adds d to tree[i], then i += i & -i, until i > n.

Pitfalls: 1-indexed positions (Fenwick trees cannot use index 0); 64-bit sums (up to 2 * 10^14); the delta can be negative; print with one buffered write.

Complexity

O((n + q) log n) time, O(n) memory.

Solutions

Written from scratch and checked by compiling and running each one against a brute force on random inputs. Fast input/output, the way you would submit it.

Show solutions (C++17, Python 3, Java 17, Node.js)
#include <bits/stdc++.h>
using namespace std;

int n;
vector<long long> tree;

void update(int i, long long d) {
    for (; i <= n; i += i & -i) tree[i] += d;
}

long long query(int i) {
    long long s = 0;
    for (; i > 0; i -= i & -i) s += tree[i];
    return s;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int q;
    cin >> n >> q;
    tree.assign(n + 1, 0);
    vector<long long> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        update(i, a[i]);
    }
    string out;
    while (q--) {
        int type;
        cin >> type;
        if (type == 1) {
            int k;
            long long u;
            cin >> k >> u;
            update(k, u - a[k]);
            a[k] = u;
        } else {
            int l, r;
            cin >> l >> r;
            out += to_string(query(r) - query(l - 1));
            out += '\n';
        }
    }
    cout << out;
    return 0;
}

Problem: CSES Problem Set (Antti Laaksonen, University of Helsinki), CC BY-NC-SA 4.0. The explanations and code above are original (© Anupam Kumar).

Related problems

Back to the sheet