Harmonic Lemma
Key Points
- •The Harmonic Lemma says that the values of n/i only change about 2 times, so you can iterate those value blocks in O() instead of O(n).
- •A classic identity is d(i) = n/i , letting you compute the summatory divisor count in O().
- •Block decomposition uses r = n / n/l to jump to the end of a constant-value segment in O(1).
- •The sum n/i is O(n n) because it behaves like n times the harmonic series.
- •This trick powers many number-theory and DP optimizations whenever indices appear as n/k or n/i .
- •You can combine block decomposition with prefix sums to compute expressions like a[i] n/i in O().
- •In problems with floor-division structure, block iteration often turns an O(n) loop into O() while keeping memory O(1) or O(n) for prefix sums.
- •The method is sometimes called the hyperbola method or integer-division trick in competitive programming.
Prerequisites
- →Floor and ceiling functions — Understanding how ⌊x⌋ behaves and how to invert inequalities is the basis of deriving block endpoints.
- →Harmonic series and logarithms — The O(n log n) bound uses that H_n grows like log n.
- →Prefix sums — Needed to aggregate weighted ranges [l, r] in O(1).
- →Basic number theory (divisors) — Identities like ∑ d(i) = ∑ ⌊n/k⌋ arise from counting divisor pairs.
- →Integer overflow awareness — Block computations involve products like v·len that may exceed 32-bit limits.
Detailed Explanation
Tap terms for definitions01Overview
Imagine looping i = 1..n and reading the integer part of n divided by i, i.e., floor(n/i). At first glance, this seems to need O(n) steps. The Harmonic Lemma reveals a hidden structure: floor(n/i) does not change at every i; it stays constant over long stretches (blocks) and only changes about 2√n times. This lets us jump over entire blocks at once. The consequence is powerful: many sums involving floor(n/i) can be evaluated in O(√n) instead of O(n). Hook: Instead of reading every page of a 1000-page book, suppose you could read only the pages where the topic changes—maybe just 60–70 pages. The Harmonic Lemma is like flipping to those change points for floor divisions. Concept: The key observation is that if floor(n/i) = v, then all i in some contiguous interval share the same value v. With simple algebra, we can compute r from l (or l from v) in O(1). That means a for loop can jump directly from one block to the next, processing each block in constant time. Example: To compute S = ∑_{i=1}^{n} floor(n/i), we iterate over blocks where floor(n/i) is constant v. We add −l+1) to S and jump l to r+1. This yields an O(√n) algorithm with tiny memory.
02Intuition & Analogies
Think of pouring candies (n candies) into bags of sizes i = 1, 2, 3, …; the number of full bags you can fill is floor(n/i). For small i, the bag is small, so you fill many bags (large value). For large i, the bag is large, so you fill few bags (small value). As i increases, floor(n/i) decreases, but not one-by-one every step. It often stays the same across several consecutive i’s—those are blocks. For example, with n = 100, floor(100/1)=100, floor(100/2)=50, floor(100/3)=33, …; near i = 50..100, many consecutive i give 1 or 2. If you only care about how many full bags you get (the value), you can jump to the next i where that number changes, skipping everything in between. A useful mental picture: Write all divisors of numbers up to n as grid points (i, j) with ≤ n. Counting by rows (fix i) is like summing floor(n/i); counting by columns (fix floor(n/i)) groups many points together. The border = n is a hyperbola, which is why this trick is sometimes called the hyperbola method. Below the hyperbola, points cluster heavily near the axes, meaning many i share the same floor value—perfect for block jumps. In daily life, if bus arrivals are grouped in time windows, you don’t check every minute; you check when the schedule changes. Likewise, we step through i only where floor(n/i) changes, which happens about 2√n times.
03Formal Definition
04When to Use
Use this technique when your summation or DP transitions repeatedly use terms of the form \lfloor n/i \rfloor or n/i as an index/value. Common cases:
- Summatory divisor problems: Compute \su d(i) using the identity \su d(i) = \su \lfloor n/k \rfloor.
- Hyperbola-method rearrangements: Split lattice point counts under ≤ n into two short sums around .
- Weighted sums: Evaluate \su a(i),\lfloor n/i \rfloor using block decomposition plus a prefix sum of a(i).
- DP with floor transitions: If transitions depend only on v = \lfloor n/i \rfloor, you can process ranges with the same v together.
- Sieve-like computations: In multiplicative-function summatory problems that involve \lfloor n/i \rfloor (e.g., using Möbius or Euler’s totient in convolution identities), block iteration reduces runtime from O(n) to O() when combined with precomputed prefix sums or small sieves. Aim for this method when constraints make O(n) too slow (n up to 1e12 in math-y problems, or n up to 1e7–1e8 with tight time limits) and the structure involves integer division by i.
⚠️Common Mistakes
- Off-by-one in block boundaries: The interval is with r = \lfloor n / \lfloor n/l \rfloor \rfloor. Forgetting the floor around n/l or using r = \lfloor n/(\lfloor n/l\rfloor+1) \rfloor is incorrect.
- Using 32-bit integers: For n up to 1e12 or more, use 64-bit integers (long long in C++). Multiplications like v*(r-l+1) can overflow 32-bit types.
- Forgetting that weights vary: Block decomposition assumes the floor value is constant, but if your term includes i itself (e.g., a(i) depends on i), you need a prefix sum to aggregate over in O(1); otherwise you revert to O(n).
- Double counting near in hyperbola splits: When using 2∑_{i≤√n} ⌊n/i⌋ − ⌊√n⌋^2, check whether the diagonal i = j is counted once.
- Assuming O() memory is always enough: Some applications (Möbius/φ summatory) also need sieve arrays or memoization; memory may grow to O(n) if you’re not careful.
- Missing termination: Always advance l to r+1. If you advance by +1, you lose the speedup and risk infinite loops when values repeat.
Key Formulas
Harmonic bound
Explanation: Since n/i ≤ n/i, the sum is at most n times the harmonic number . Because grows like log n, the overall sum is O(n log n).
Block interval
Explanation: All indices i that give the same floor value v form one contiguous block. The left and right endpoints are computed from n and v.
Jump-to-right endpoint
Explanation: Given the current left endpoint l, the floor value v = n/l stays constant until r. This formula computes that r directly in O(1).
Divisor-sum identity
Explanation: Each pair (d, m) with d· corresponds to a divisor of some number up to n. Counting by d or by the quotient gives this identity, enabling O(√n) computation.
Hyperbola split
Explanation: The lattice points under are symmetric across . Doubling the count up to √n and subtracting the diagonal avoids double counting.
Harmonic asymptotic
Explanation: The harmonic number grows like the natural logarithm plus Euler–Mascheroni constant It explains the n log n behavior of related sums.
Distinct values count
Explanation: Values above √n occur only O(√n) times; values below √n are at most √n distinct. Together this bounds the number of blocks.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // Computes S(n) = sum_{i=1}^{n} floor(n / i) using harmonic block iteration. 5 // Time: O(sqrt(n)), Space: O(1) 6 7 int main() { 8 ios::sync_with_stdio(false); 9 cin.tie(nullptr); 10 11 long long n; 12 if (!(cin >> n)) return 0; 13 14 long long ans = 0; 15 for (long long l = 1; l <= n; ) { 16 long long v = n / l; // current constant value of floor(n / i) 17 long long r = n / v; // last index with the same value 18 // All i in [l, r] have floor(n / i) == v 19 ans += v * (r - l + 1); // add v for each i in the block 20 l = r + 1; // jump to the next block 21 } 22 23 cout << ans << '\n'; 24 return 0; 25 } 26
We iterate only over blocks of indices where floor(n/i) is constant. For each block [l, r], the value v = n/l persists, and r = n/v is computed in O(1). Each block contributes v times its length to the sum. The loop runs about 2\sqrt{n} iterations.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // Computes D(n) = sum_{i=1}^{n} d(i) using the identity 5 // D(n) = sum_{k=1}^{n} floor(n / k) and harmonic blocks. 6 // Time: O(sqrt(n)), Space: O(1) 7 8 int main() { 9 ios::sync_with_stdio(false); 10 cin.tie(nullptr); 11 12 long long n; 13 if (!(cin >> n)) return 0; 14 15 long long D = 0; 16 for (long long l = 1; l <= n; ) { 17 long long v = n / l; // quotient value 18 long long r = n / v; // last k with floor(n/k) == v 19 // There are (r - l + 1) terms each equal to v 20 D += v * (r - l + 1); 21 l = r + 1; 22 } 23 24 cout << D << '\n'; 25 return 0; 26 } 27
Each integer m ≤ n contributes 1 to d(i) for every divisor i of m. Counting pairs (i, m/i) with i·(m/i) ≤ n via the quotient k = \lfloor n/i \rfloor yields the identity D(n) = ∑_{k=1}^{n} \lfloor n/k \rfloor. We then compute this sum in O(\sqrt{n}) using block jumps.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // Given an array a[1..n], compute S = sum_{i=1}^{n} a[i] * floor(n / i) 5 // using harmonic blocks and a prefix-sum array. 6 // Time: O(n + sqrt(n)), Space: O(n) 7 8 int main() { 9 ios::sync_with_stdio(false); 10 cin.tie(nullptr); 11 12 int n; 13 if (!(cin >> n)) return 0; 14 vector<long long> a(n + 1), pref(n + 1); 15 for (int i = 1; i <= n; ++i) cin >> a[i]; 16 17 // Build prefix sums: pref[i] = a[1] + ... + a[i] 18 for (int i = 1; i <= n; ++i) pref[i] = pref[i - 1] + a[i]; 19 20 auto range_sum = [&](int l, int r) -> long long { 21 if (l > r) return 0LL; 22 return pref[r] - pref[l - 1]; 23 }; 24 25 long long ans = 0; 26 for (long long l = 1; l <= n; ) { 27 long long v = n / l; // constant multiplier for this block 28 long long r = n / v; // last index with same floor value 29 if (r > n) r = n; // guard, though n/v <= n always 30 // Sum a[l..r] in O(1) via prefix sums, then multiply by v 31 ans += v * range_sum((int)l, (int)r); 32 l = r + 1; 33 } 34 35 cout << ans << '\n'; 36 return 0; 37 } 38
If a weight depends on i, we combine block decomposition with a prefix-sum array. Each block [l, r] contributes v times the sum of a[i] over that block, which we obtain in O(1) using prefix sums. The number of blocks is O(\sqrt{n}), so the main loop is fast.