Number-theoretic block decomposition can quickly calculate sums of the form
If can be calculated in time, or the prefix sums of have already been precomputed, number-theoretic block decomposition can calculate the value of the above sum in time.
Number-theoretic block decomposition is often combined with techniques such as the Möbius inversion.
Idea
First, this article uses a simple example to explain the idea of number-theoretic block decomposition. Suppose we want to count the lattice points under the hyperbola shown in the figure:
This is equivalent to calculating the sum:
This is the special case of the sum above with .
The simplest method is of course to calculate each column and then sum them, but this requires calculating the number of lattice points in each of columns . From the figure, we can see that these lattice-point columns can be divided into blocks. The columns within each block have the same height and form a rectangular lattice. Therefore, as long as we know the widths of these blocks, we can quickly finish the count by calculating the sizes of the rectangular blocks.
This is the basic idea of divisor block decomposition.
Properties
This section discusses several conclusions about block decomposition of lattice points under the hyperbola . Specifically, the integers from need to be divided into blocks according to the values of . Let
This is the set of all possible values of .
First, there are only different values of this form. Therefore, the number of blocks obtained by number-theoretic block decomposition is only .
Property 1
.
Proof
Consider two cases:
- When , there are at most possible values of , so there are also at most possible values of .
- When , , so there are again at most possible values.
Therefore, the total number of possible values is .
With a more detailed analysis, the set and its size can in fact be described exactly.
Property 2
Let . The elements of in increasing order are
Consequently, .
Proof
First, for , one can prove that . Let ; we need to prove . Written as inequalities, this means that given , we need to prove . The given condition can be written as , so it is enough to prove , that is, . This is equivalent to , which holds for every .
This result actually shows that the mapping is a bijection between and . Since the values of are distinct, the values of are also distinct. The only possible common elements of the two sets are and . Therefore, .
To obtain the final expression for , we need to determine when holds.
- When , we always have , that is, . Thus, . Since is always odd, the left side can equivalently be relaxed to , so this condition is equivalent to , that is, .
- When , we always have , that is, . Since , we have . This is equivalent to . Again using the fact that is odd, the right side can equivalently be tightened to , so this condition is equivalent to , that is, .
Summarizing these two cases gives .
Then, the left and right endpoints of an individual block are easy to determine.
Property 3
For , the range of integer values of satisfying is
Proof
Since is equivalent to
This is further equivalent to
Using , we can take the floor of this inequality, making it equivalent to
This property also reflects the symmetry of the graph: the set of right endpoints of all blocks (the green points in the figure above) is exactly . This is easy to understand because the entire graph is symmetric about the line .
Besides these properties, the set also has a useful recursive property:
Property 4
For , .
Proof
Let . Then, for every ,
so . The second equality uses the property of the floor function for nested fractions.
As mentioned above, is both the set of values of in each block and the set of right endpoints of the blocks. This means that if number-theoretic block decomposition is applied recursively (that is, the value of a function at depends on its values at ), then the sets of values and right endpoints involved throughout the computation are both actually . A typical example is the Du sieve.
Process
Using the conclusions described in the previous section, we obtain the concrete process of number-theoretic block decomposition.
To calculate
we can divide the indices into blocks according to the value of . Since the indices with the same value of form a consecutive integer interval , the value of the sum in that block is
To calculate this sum quickly, one generally needs to calculate the sum involving on the left quickly. In some cases, its expression is known, so a single calculation can be completed in time; in other cases, its prefix sums can be precomputed, and a single query can still be completed in time.
When calculating the left and right endpoints of each block in sequence, the left endpoint of the current block is the right endpoint of the previous block plus , while the right endpoint of the current block is . This gives the following pseudocode:
Assuming that a single calculation of has time complexity , the time complexity of the entire process is .
Extensions
The previous section discussed the most common and basic form of number-theoretic block decomposition. This section further discusses its extensions.
Number-Theoretic Block Decomposition with Ceilings
Number-theoretic block decomposition can be used to calculate sums containing ceilings:
Since , the sum can be converted to the floor case:
Note that the upper bound of the summation changes, and the term for is handled separately.
Multidimensional Number-Theoretic Block Decomposition
Number-theoretic block decomposition can also handle sums containing more than one floor expression:
To apply the idea, it is necessary to ensure that the values of all floor expressions remain unchanged within each block. In other words, a multidimensional block should be the intersection of all one-dimensional blocks. Therefore, for a known left endpoint , the corresponding right endpoint is
That is, take the minimum of the right endpoints of all one-dimensional blocks as the right endpoint of the multidimensional block. The figure below can help illustrate this:
The two-dimensional form is relatively common. In this case, replace in the pseudocode above with
Number-Theoretic Block Decomposition with Arbitrary Exponents
Number-theoretic block decomposition can be used to calculate sums containing floor expressions with arbitrary exponents:
Here, are positive real numbers. In the basic form discussed in this article, .
Property
For a positive integer and positive real numbers , let
Then,
-
.
-
For , the range of satisfying is
Proof
For the first point, consider two cases:
- When , we have , so there are at most possible values of .
- When , we have , so , and there are again at most possible values of .
Combining the two cases gives .
For the second point, is equivalent to
Taking floors of this inequality gives the second statement.
Using these properties, number-theoretic block decomposition for arbitrary exponents can be implemented in time.
Example
For example, for , consider the sum
which can be solved by number-theoretic block decomposition in time. When the left endpoint of a block is , the right endpoint can be calculated as .
Examples
UVa11526 H(n)
data sets are given, each containing an integer . For each data set, output .
Solution
Based on the analysis above, all positions with the same can be calculated together. The time complexity is .
Implementation
#include <iostream>
long long H(int n) {
long long res = 0; // store the result
int l = 1, r; // left and right endpoints of the block
while (l <= n) {
r = n / (n / l); // calculate the right endpoint of the current block
// Accumulate this block's contribution to the result. Multiplying by 1LL prevents overflow
res += 1LL * (r - l + 1) * (n / l);
l = r + 1; // move the left endpoint to the next block
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t, n;
std::cin >> t;
while (t--) {
std::cin >> n;
std::cout << H(n) << '\n';
}
return 0;
}
Codeforces 1954E Chain Reaction
There is a row of monsters, each with initial health . One attack decreases the health of a contiguous segment of living monsters by ; health no greater than is considered dead. For every , find the number of attacks required to kill all monsters. Here .
Solution
Let . Suppose killing the first monsters requires attacks, and the health of the -th monster is . Since killing the -th monster requires attacking it times, all these attacks can extend to the -th monster. Therefore, to kill the -th monster, only more attacks are needed. Thus, the total number of attacks is
Since the involved in the problem can be large, calculating this sum separately for every is infeasible. We can maintain the sequence for every . Initially, let . Suppose the sequence is known. To obtain , according to the analysis above, we only need to add to the -th term. Using multidimensional number-theoretic block decomposition, this modification can be divided into interval modification operations, with a fixed value added on each interval. The resulting sequence is the answer.
Since the problem involves a series of interval-addition operations and queries occur only after all modifications are complete, a difference sequence can be maintained for interval-addition modifications, and the required sequence can finally be obtained by taking prefix sums. The total time complexity is . This problem also has other solutions.
Implementation
#include <algorithm>
#include <iostream>
constexpr int N = 100009;
int n, a[N], maxn;
long long ans[N];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n;
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
maxn = std::max(maxn, a[i]);
}
for (int i = 0; i < n; ++i)
for (int l = 1, r;; l = r + 1) {
r = std::min(l < a[i] ? (a[i] - 1) / ((a[i] - 1) / l) : N,
l < a[i + 1] ? (a[i + 1] - 1) / ((a[i + 1] - 1) / l)
: N); // two-dimensional number-theoretic block decomposition
if (r == N) break;
int x = (a[i + 1] - 1) / l - std::max(a[i] - 1, 0) / l;
if (x > 0) ans[l] += x, ans[r + 1] -= x; // accumulate contribution
}
++ans[0]; // the formula ⌈a/l⌉=(a-1)/l+1 does not hold when a=0, so it needs to be corrected
for (int i = 1; i <= maxn; ++i)
std::cout << (ans[i] += ans[i - 1]) << " \n"[i == maxn];
return 0;
}