Sqrt Decomposition

Introduction

Sqrt Decomposition is a technique that divides data into √n blocks to achieve O(√n) time complexity for range queries and updates, balancing between naive O(n) and complex O(log n) solutions.

Key Concepts

  • Divide array into √n blocks (each of size √n)
  • Precompute block-level information
  • For queries: Combine results from complete blocks and partial elements

Mathematics Behind Sqrt Decomposition

The algorithm is based on the inequality:

√n × √n ≥ n

This ensures:

  • Number of blocks: O(√n)
  • Elements per block: O(√n)
  • Total operations per query: O(√n)

Optimal Block Size: For array of size n, optimal block size is k = √n, minimizing the sum of:

  • O(n/k) block operations
  • O(k) element operations

Time and Space Complexity

  • Preprocessing: O(n)
  • Range Query: O(√n)
  • Point Update: O(1)
  • Range Update: O(√n)
  • Space: O(n)

Problem Examples

Easy Problems

1. Range Sum Queries (Codeforces 633B)

Given an array, answer range sum queries and support point updates.

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

vector<int> arr, block_sum;
int block_size;

void build(int n) {
    block_size = sqrt(n);
    block_sum.resize((n + block_size - 1) / block_size, 0);
    
    for (int i = 0; i < n; i++)
        block_sum[i / block_size] += arr[i];
}

void update(int idx, int val) {
    int block_idx = idx / block_size;
    block_sum[block_idx] += val - arr[idx];
    arr[idx] = val;
}

int query(int l, int r) {
    int sum = 0;
    while (l <= r && l % block_size != 0) {
        sum += arr[l];
        l++;
    }
    while (l + block_size <= r) {
        sum += block_sum[l / block_size];
        l += block_size;
    }
    while (l <= r) {
        sum += arr[l];
        l++;
    }
    return sum;
}

int main() {
    int n = 8;
    arr = {1, 3, 5, 7, 9, 11, 13, 15};
    build(n);
    
    cout << query(1, 5) << endl; // 3+5+7+9+11 = 35
    update(3, 10);
    cout << query(1, 5) << endl; // 3+5+10+9+11 = 38
    
    return 0;
}

Input/Output Example

Input: (built into code)
Array: [1, 3, 5, 7, 9, 11, 13, 15]

Output:
35
38

2. Range Minimum Queries (Codeforces 514B)

Find minimum in range with point updates.

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

vector<int> arr, block_min;
int block_size;

void build(int n) {
    block_size = sqrt(n);
    block_min.resize((n + block_size - 1) / block_size, INT_MAX);
    
    for (int i = 0; i < n; i++)
        block_min[i / block_size] = min(block_min[i / block_size], arr[i]);
}

void update(int idx, int val) {
    int block_idx = idx / block_size;
    arr[idx] = val;
    int start = block_idx * block_size;
    int end = min(start + block_size, (int)arr.size());
    block_min[block_idx] = INT_MAX;
    for (int i = start; i < end; i++)
        block_min[block_idx] = min(block_min[block_idx], arr[i]);
}

int query(int l, int r) {
    int mn = INT_MAX;
    while (l <= r && l % block_size != 0) {
        mn = min(mn, arr[l]);
        l++;
    }
    while (l + block_size <= r) {
        mn = min(mn, block_min[l / block_size]);
        l += block_size;
    }
    while (l <= r) {
        mn = min(mn, arr[l]);
        l++;
    }
    return mn;
}

int main() {
    int n = 8;
    arr = {5, 3, 8, 1, 7, 4, 2, 6};
    build(n);
    
    cout << query(1, 5) << endl; // min(3,8,1,7,4) = 1
    update(3, 10);
    cout << query(1, 5) << endl; // min(3,8,10,7,4) = 3
    
    return 0;
}

Intermediate Problems

1. Range Sum with Range Update (Codeforces 52C)

Support range addition and range sum queries.

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

vector<long long> arr, block_sum, block_add;
int block_size;

void build(int n) {
    block_size = sqrt(n);
    block_sum.resize((n + block_size - 1) / block_size, 0);
    block_add.resize((n + block_size - 1) / block_size, 0);
    
    for (int i = 0; i < n; i++)
        block_sum[i / block_size] += arr[i];
}

void range_update(int l, int r, int val) {
    while (l <= r && l % block_size != 0) {
        arr[l] += val;
        block_sum[l / block_size] += val;
        l++;
    }
    while (l + block_size <= r) {
        block_add[l / block_size] += val;
        block_sum[l / block_size] += val * block_size;
        l += block_size;
    }
    while (l <= r) {
        arr[l] += val;
        block_sum[l / block_size] += val;
        l++;
    }
}

long long range_query(int l, int r) {
    long long sum = 0;
    while (l <= r && l % block_size != 0) {
        sum += arr[l] + block_add[l / block_size];
        l++;
    }
    while (l + block_size <= r) {
        sum += block_sum[l / block_size];
        l += block_size;
    }
    while (l <= r) {
        sum += arr[l] + block_add[l / block_size];
        l++;
    }
    return sum;
}

int main() {
    int n = 8;
    arr = {1, 3, 5, 7, 9, 11, 13, 15};
    build(n);
    
    cout << range_query(1, 5) << endl; // 3+5+7+9+11 = 35
    range_update(2, 6, 2);
    cout << range_query(1, 5) << endl; // 3+7+9+11+13 = 43
    
    return 0;
}

2. Mo's Algorithm (Codeforces 86D)

Count distinct elements in range queries (offline processing).

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

struct Query {
    int l, r, idx;
};

vector<int> arr, cnt, ans;
vector<Query> queries;
int curr_ans = 0;

void add(int x) {
    if (cnt[x] == 0) curr_ans++;
    cnt[x]++;
}

void remove(int x) {
    cnt[x]--;
    if (cnt[x] == 0) curr_ans--;
}

int main() {
    int n = 8, q = 3;
    arr = {1, 2, 3, 1, 1, 2, 1, 2};
    cnt.resize(4, 0);
    queries = {{0, 4, 0}, {1, 6, 1}, {2, 7, 2}};
    ans.resize(q);
    
    int block_size = sqrt(n);
    sort(queries.begin(), queries.end(), [&](Query a, Query b) {
        if (a.l / block_size != b.l / block_size)
            return a.l < b.l;
        return (a.l / block_size % 2) ? a.r > b.r : a.r < b.r;
    });
    
    int l = 0, r = -1;
    for (Query q : queries) {
        while (l > q.l) add(arr[--l]);
        while (r < q.r) add(arr[++r]);
        while (l < q.l) remove(arr[l++]);
        while (r > q.r) remove(arr[r--]);
        ans[q.idx] = curr_ans;
    }
    
    for (int x : ans) cout << x << " "; // 3 3 2
    return 0;
}

Hard Problem

1. Online Range Mode Query (Codeforces 840D)

Find the most frequent element in a range that appears more than threshold times.

#include <iostream>
#include <vector>
#include <unordered_map>
#include <cmath>
#include <algorithm>
using namespace std;

vector<int> arr;
vector<vector<pair<int, int>>> block_mode;
vector<unordered_map<int, int>> block_freq;
int block_size;

void build(int n) {
    block_size = sqrt(n);
    int blocks = (n + block_size - 1) / block_size;
    block_mode.resize(blocks);
    block_freq.resize(blocks);
    
    for (int b = 0; b < blocks; b++) {
        int start = b * block_size;
        int end = min(start + block_size, n);
        unordered_map<int, int> freq;
        vector<pair<int, int>> mode;
        
        for (int i = start; i < end; i++) {
            freq[arr[i]]++;
            mode.emplace_back(freq[arr[i]], arr[i]);
        }
        sort(mode.rbegin(), mode.rend());
        block_mode[b] = mode;
        block_freq[b] = freq;
    }
}

int query(int l, int r, int threshold) {
    unordered_map<int, int> freq;
    int res = -1;
    
    while (l <= r && l % block_size != 0) {
        freq[arr[l]]++;
        if (freq[arr[l]] > threshold && (res == -1 || arr[l] < res))
            res = arr[l];
        l++;
    }
    while (l + block_size <= r) {
        int b = l / block_size;
        for (auto [cnt, val] : block_mode[b]) {
            if (cnt + freq[val] > threshold) {
                if (res == -1 || val < res) res = val;
            } else break;
        }
        for (auto [val, cnt] : block_freq[b]) {
            freq[val] += cnt;
        }
        l += block_size;
    }
    while (l <= r) {
        freq[arr[l]]++;
        if (freq[arr[l]] > threshold && (res == -1 || arr[l] < res))
            res = arr[l];
        l++;
    }
    return res;
}

int main() {
    int n = 10, q = 2;
    arr = {1, 2, 3, 1, 2, 1, 1, 3, 2, 1};
    build(n);
    
    cout << query(1, 7, 2) << endl; // 1 (appears 4 times)
    cout << query(0, 9, 3) << endl; // 1 (appears 5 times)
    
    return 0;
}