Introduction
The Euclidean-like algorithm was proposed by Hong Huadun during the 2016 Winter Camp camper exchange. It is commonly used to solve summation problems for sequences of the form
for sequences of this form (indexed by ). Its main idea is to use the recursive structure of fractions to reduce the problem to a smaller one and solve it recursively. Because the recursive structure of fractions has a direct connection with the Euclidean algorithm and a direct link to it in continued-fraction expansion, this summation method is also called the Euclidean-like algorithm.
Methods such as continued fractions and the Stern–Brocot tree also characterize the recursive structure of fractions, so problems solvable with the Euclidean-like algorithm can usually be solved with these methods as well. Compared with them, the Euclidean-like algorithm is generally easier to understand and its implementation is more concise.
Euclidean-like Algorithm
The simplest example is the following summation problem:
where are all positive integers.
Algebraic Solution
First, taking modulo simplifies the problem and reduces it to the case :
Now consider the transformed problem. Let
Then the original problem can be written as a double sum:
To reverse the order of summation, we need to compute, for each , the range of satisfying the condition. To do so, transform the condition:
The properties of the floor and ceiling functions were used repeatedly during these transformations. Substituting the transformed condition, the original expression becomes:
Let ; this returns us to the case discussed above.
Combining these two transformations, we see that are repeatedly reduced modulo one another and swapped until . This resembles applying the Euclidean algorithm to , which is why the method is called the Euclidean-like algorithm. Its time complexity is .
During the computation, the case may occur, in which case the inner recursion has . This does not affect the final result. However, if the algorithm is terminated immediately when , its time complexity can be improved to .
Complexity Explanation
The similarity between this algorithm and the Euclidean algorithm makes it easy to show that its time complexity is . Therefore, it only remains to show that if the algorithm terminates when , its time complexity is also .
Let , and write and . These correspond respectively to the area of the lattice-point diagram and the slope of the line in the geometric interpretation (see the next section). For sufficiently large , we approximately have .
Consider how and change during the algorithm. In the first modulo operation, remains unchanged, and approximately changes from to , which corresponds to changing the slope from to ; also approximately becomes times its original value. In the second operation, which swaps the horizontal and vertical coordinates, remains approximately unchanged while becomes its reciprocal. Thus, if the pair becomes after the two operations, then and .
Because , after two rounds of recursive computation, the minimum shrinkage factor of the product is
Therefore, the algorithm must terminate after at most rounds. Starting from the second round, the at the beginning of each round is never greater than the after the modulo operation in the preceding round; the latter is approximately with , so . This proves the claim above.
The reference implementation for the template problem is as follows:
Template Problem Implementation (Library Checker - Sum of Floor of Linear)
#include <iostream>
long long solve(long long a, long long b, long long c, long long n) {
long long n2 = n * (n + 1) / 2;
if (a >= c || b >= c)
return solve(a % c, b % c, c, n) + (a / c) * n2 + (b / c) * (n + 1);
long long m = (a * n + b) / c;
if (!m) return 0;
return m * n - solve(c, c - b - 1, a, m - 1);
}
int main() {
int t;
std::cin >> t;
for (; t; --t) {
int a, b, c, n;
std::cin >> n >> c >> a >> b;
std::cout << solve(a, b, c, n - 1) << '\n';
}
return 0;
}
Geometric Intuition
This algorithm can also be understood geometrically. The main problems solvable by the Euclidean-like algorithm are lattice-point counting problems below a line.
As shown in the leftmost part of the figure below, the sum is equivalent to counting the lattice points below the line
above the -axis (excluding the -axis), whose horizontal coordinates lie in .
First remove the integer parts of the slope and intercept. This is equivalent to separately counting the blue points in the middle part of the figure above. When the slope and intercept are both integers, the blue points form a trapezoidal array: the lattice points in different columns form arithmetic progressions, so their number is easy to compute. After removing these points, the remaining lattice points match the number of red points in the rightmost part of the figure. The problem is reduced to the case where both the slope and intercept are less than one. Since the trapezoid has height and its two bases have lengths and , the trapezoid area formula gives
Next, flip the horizontal and vertical axes. As shown in the leftmost part of the figure below, the red and blue points form a rectangular lattice of horizontal length and vertical length . To count the red points, count the blue points and subtract that number from the number of points in the rectangular lattice. After the flip, the blue lattice in the left half of the figure becomes the red lattice below a line. Moreover, after the flip, the slope is greater than one, returning to the case handled above.
The key is how to calculate the equation of the line above the new red lattice. Flipping the coordinate axes in the leftmost part of the figure gives the middle part. The line above the flipped red lattice (the solid line in the middle) does not correspond to the original line (the solid line on the left); instead, it is obtained by shifting the original line slightly to the upper left (the dashed line on the left). This is because directly flipping the line (the solid line on the left) produces the dashed line in the middle, but by definition the lattice points below it include those exactly on the line, causing points on the line to be counted twice. To avoid this, after flipping to obtain , shift the resulting line slightly downward to obtain ; the lattice below it is then exactly the blue lattice before the flip.
There is one more detail to handle. The intercept of the line in the middle part of the figure is negative, so we have not yet returned to the original situation. To make the intercept nonnegative, shift the line (the solid line in the middle) one unit to the left. This does not omit any lattice point because the blue lattice before the flip contains no point with vertical coordinate zero, so after the flip there is no point with horizontal coordinate zero either. The line equation finally becomes ; at the same time, the upper bound of the lattice's horizontal coordinate changes from to . This step can be summarized as
This recursive algorithm works mainly for two reasons:
- First, the slope of the line repeatedly takes its fractional part and then its reciprocal, which is equivalent to computing the continued-fraction expansion of the line's slope . Since the continued-fraction expansion of a rational fraction has length , this process must terminate after steps;
- Second, because the slope of the line is less than one whenever the axes are flipped, intuition suggests that ; that is, the range of horizontal coordinates keeps shrinking after each such iteration. The complexity analysis above rigorously shows that after every two iterations, is at most half its previous value, so this process must terminate after steps.
This is also why the Euclidean-like algorithm has complexity when the slope is rational.
The Euclidean-like algorithm can be extended to the case of an irrational slope using similar geometric intuition; see the examples later in the article for the detailed analysis.
Examples
[Template] Euclidean-like Algorithm
Multiple queries. Given positive integers , calculate
Solution 1
As in the derivation for , recursive expressions for can be obtained.
First, use modulo reduction to reduce the problem to the case :
Then, reversing the order of summation gives a further transformation. Similarly, let
For the sum , we have
For the sum , we have
From the geometric viewpoint, these nonlinear sums amount to assigning an appropriate weight to every point in the region. Apart from these weights, the rest of the computation is exactly the same. In general, the weights can be chosen as follows:
Another feature of this problem is that and interleave during the recursive computation. Therefore, must be recursed together as a triple.
#include <iostream>
struct Data {
int f, g, h;
};
Data solve(long long a, long long b, long long c, long long n) {
constexpr long long M = 998244353;
constexpr long long i2 = (M + 1) / 2;
constexpr long long i6 = (M + 1) / 6;
long long n2 = (n + 1) * n % M * i2 % M;
long long n3 = (2 * n + 1) * (n + 1) % M * n % M * i6 % M;
Data res = {0, 0, 0};
if (a >= c || b >= c) {
auto tmp = solve(a % c, b % c, c, n);
long long aa = a / c, bb = b / c;
res.f = (tmp.f + aa * n2 + bb * (n + 1)) % M;
res.g = (tmp.g + aa * n3 + bb * n2) % M;
res.h = (tmp.h + 2 * bb * tmp.f % M + 2 * aa * tmp.g % M +
aa * aa % M * n3 % M + bb * bb % M * (n + 1) % M +
2 * aa * bb % M * n2 % M) %
M;
return res;
}
long long m = (a * n + b) / c;
if (!m) return res;
auto tmp = solve(c, c - b - 1, a, m - 1);
res.f = (m * n - tmp.f + M) % M;
res.g = (m * n2 + (M - tmp.f) * i2 + (M - tmp.h) * i2) % M;
res.h = (n * m % M * m - tmp.f - tmp.g * 2 + 3 * M) % M;
return res;
}
int main() {
int t;
std::cin >> t;
for (; t; --t) {
int n, a, b, c;
std::cin >> n >> a >> b >> c;
auto res = solve(a, b, c, n);
std::cout << res.f << ' ' << res.h << ' ' << res.g << '\n';
}
return 0;
}
Solution 1
If is a perfect square, then when is even, the sum is ; otherwise, depending on the parity of , the sum alternates between and . Now consider the case where is not a perfect square.
To apply the Euclidean-like algorithm, first transform the sum into a familiar form:
The function has the form
Unlike the algorithm in the main text, the slope here is no longer rational. Let the slope be
We again discuss two cases. If , then
The problem is reduced to the case where the slope is less than one. If , let . Then
In this derivation, the condition for exchanging and is simpler than in the main text because the line has no lattice points other than the origin. The key is to write the exchanged sum in the form of , which requires to satisfy
This is not difficult: rationalizing the denominator gives
Therefore,
This gives
To avoid integer overflow, divide by their greatest common divisor at every step. Because this computation is exactly the same as computing the continued fraction of , the theory of continued fractions shows that, as long as , they necessarily remain within the integer range during the computation. In addition, although does not overflow, under this problem's data limits may exceed the range of -bit integers. Natural overflow is sufficient and no extra handling is needed; the final result is always in .
Although the slope never becomes zero, the algorithm still has complexity , as can be seen from the preceding complexity argument.
#include <cmath> #include <iostream> long long r; long double sqrt_r; long long gcd(long long a, long long b) { return b ? gcd(b, a % b) : a; } unsigned long long f(long long a, long long b, long long c, long long n) { if (!n) return 0; auto d = gcd(a, gcd(b, c)); a /= d; b /= d; c /= d; unsigned long long k = (a * sqrt_r + b) / c; if (k) { return n * (n + 1) / 2 * k + f(a, b - c * k, c, n); } else { unsigned long long m = n * (a * sqrt_r + b) / c; return n * m - f(c * a, -c * b, a * a * r - b * b, m); } } unsigned long long solve(long long n, long long r) { long long sqr = sqrt_r = sqrtl(r); if (r == sqr * sqr) return r % 2 ? (n % 2 ? -1 : 0) : n; return n - 2 * f(1, 0, 1, n) + 4 * f(1, 0, 2, n); } int main() { int t; std::cin >> t; for (; t; --t) { int n; std::cin >> n >> r; long long res = solve(n, r); std::cout << res << '\n'; } return 0; }
Fraction
Given positive integers , find all values satisfying $a/b [!note]- Solution
This problem is also a classic application of the Stern–Brocot tree; related solutions can be found here. Since it relies only on the recursive structure of fractions, it can likewise be solved using a Euclidean-like algorithm, and can therefore be viewed as an application of the Euclidean-like algorithm.
If there is at least one natural number strictly between and , we can directly take . Otherwise, necessarily
From this inequality, the integer part of is . Remove that integer part directly and then take the reciprocal of the whole expression to determine its fractional part. This is exactly the basic method for determining the continued fraction of . If the final answer is , the algorithm has time complexity .
There is one subtle issue here: is the lexicographically smallest fraction obtained after taking the reciprocal also the lexicographically smallest fraction before taking the reciprocal? In other words, among fractions satisfying $a/b
Therefore, is strictly smaller than the current solution under either lexicographic order. This contradicts the assumption, so the algorithm above is correct.
#include <iostream>
void solve(int a, int b, int& p, int& q, int c, int d) {
if ((a / b + 1) * d < c) {
p = a / b + 1;
q = 1;
} else {
solve(d, c - d * (a / b), q, p, b, a % b);
p += q * (a / b);
}
}
int main() {
int a, b, c, d, p, q;
while (std::cin >> a >> b >> c >> d) {
solve(a, b, p, q, c, d);
std::cout << p << '/' << q << '\n';
}
return 0;
}
Universal Euclidean Algorithm
The derivation of the Euclidean-like algorithm discussed in the previous section is usually rather cumbersome, and the sums it can solve are mainly those reducible to (weighted) lattice-point counting below a line. This section presents a more general method that abstracts the process above and can solve more problems. It is therefore also called the universal Euclidean algorithm. It likewise uses the recursive structure of fractions, but its approach to reducing the problem differs slightly from that of the Euclidean-like algorithm.
We still consider the most classical summation problem:
where are all positive integers.
Problem Transformation
Let the line with parameters be
For this line segment, define a string consisting of and , also called the operation sequence, as follows:
- The string consists of exactly 's and 's;
- The number of 's before the -th is exactly , where .
Geometrically, this is roughly equivalent to starting at the origin: write an each time a vertical grid line is crossed to the right, and write a each time a horizontal grid line is crossed upward. As shown below:
Naturally, this definition must account for several special cases:
- When an integer lattice point is passed (that is, an upward crossing and a rightward crossing occur simultaneously), write before ;
- At the start of the string, in addition to the number of grid lines crossed upward in the interval , add extra 's;
- At the end of the string, there must be no extra 's.
If any part of the geometric description is unclear, refer to the algebraic definition above for help. The geometric description helps explain the algorithm below.
The basic idea of the universal Euclidean algorithm is to regard the and in the operation sequence as elements of a certain monoid, regard the entire operation sequence as the product of elements in that monoid, and relate the final answer to this product.
For example, in this problem, define a state vector to represent the current state after crossing some horizontal and vertical grid lines from the origin. The first component is a constant, the second is the vertical coordinate , and the third is the sum being sought. Initially, . Each upward crossing increases the vertical coordinate by one, which is equivalent to right-multiplying the state vector by the matrix
Each rightward crossing adds the current vertical coordinate to the sum, which is equivalent to right-multiplying the state vector by the matrix
Therefore, the final state is the product , where is understood as the product of the matrices above. The answer is the third component of the final state.
Besides defining elements of the monoid as matrices, they can also be defined as the contribution of an operation sequence to the final result, with the product of operations defined as the combination of the contributions of two operation sequences.
For this problem, define the contribution of each operation sequence as . To explain this notation rigorously, regard each component as a function of the operation sequence: for an operation sequence , its contribution is . Here, and are respectively the numbers of and in , that is, the numbers of rightward and upward grid-line crossings. In general, the summation symbol in the last term is defined as follows: for a function on operation sequences, define , or write it as , by the expression below:
Here, is the -th character of , and is the prefix consisting of the first characters of . In other words, this summation notation can be viewed as summing over all prefixes of that end in . For example,
Also, is the accumulated number of previous upward grid-line crossings each time a rightward grid line is crossed. For the entire operation sequence, the values of at all prefixes ending in are the values of for . Therefore, computed for the entire operation sequence is exactly the quantity required by the problem.
Initially, and . We can further define the product of two elements and as
The result of combining the contribution in the last term can be obtained as follows:
It is easy to verify that this multiplication is associative and that the identity is , so these elements form a monoid under this multiplication. The answer is the third component of the product.
Both methods produce the correct result. However, because the matrix method retains more redundant information and therefore has larger constant factors, the second method is more practical for actual problems.
Algorithm
Unlike the global reduction of the Euclidean-like algorithm, the universal Euclidean algorithm reduces the problem by combining these operations in batches. Denote the product of the operations corresponding to the string by
The reduction proceeds as follows:
-
When , the operation sequence begins with 's. Compute their product directly and remove these 's from the sequence. The number of 's before the -th is then
Therefore, this is equivalent to changing the line parameters from to . Thus, in this case,
-
When , there are at least 's before every in the operation sequence, so they can be merged into . In other words, replace by . The number of 's before the -th in the merged string is
Therefore, this is equivalent to changing the line parameters from to . Thus, in this case,
-
For the remaining case, flip the horizontal and vertical coordinates. This essentially swaps and , but the parameters of the flipped line must be calculated carefully. From the definition of the operation sequence, determine coefficients such that the number of 's before the -th in the original operation sequence is , and there are 's in total. By definition,
The number of 's before the -th is the largest such that
Therefore, . This derivation is similar to that of the Euclidean-like algorithm above and likewise uses properties of floor and ceiling functions.
There are two details to handle:
- The intercept term is negative. If the line is shifted one unit to the left, the intercept becomes nonnegative because . Therefore, extract the initial segment before the swap, and swap only the remaining 's and 's;
- After swapping and , there are extra 's at the end. Therefore, before swapping and , first extract the final segment of 's, and swap only the remaining 's and 's. The number of 's in this segment is .
After removing some characters from the beginning and end, the number of 's before the -th becomes:
Recall that the number of 's before the swap is . The one-unit left shift above requires at least one before the swap, that is, . Under this condition, there are two cases:
-
When , after handling the two points above, the operation sequence obtained by swapping and is the valid sequence corresponding to parameters . Thus,
-
In the special case , the operation sequence before the swap contains only 's, so no swap is needed and we can return directly:
Unlike the Euclidean-like algorithm, this special case of the universal Euclidean algorithm must be handled separately; otherwise negative exponents would make the computation incorrect.
These observations allow the problem to be solved recursively.
Assume that multiplying two monoid elements takes time. If powers of these elements are computed by binary exponentiation, the resulting algorithm has complexity .1
Complexity Explanation
Compared with the (Euclidean-like) algorithm, the universal Euclidean algorithm only adds binary exponentiation. The remaining computation has complexity similar to that of the Euclidean-like algorithm, already shown to be . We now need to calculate the total complexity of these binary exponentiations.
Apart from the first iteration, , so each of these iterations involves three binary exponentiations. The total complexity is:
Here, , , and . The latter two terms satisfy, respectively:
Therefore, the complexity of both terms is .
In each iteration, the line parameters change from to , and the total complexity of that iteration is
Across all recursive rounds, these terms telescope, so the final total complexity is .
Finally, adding the complexity of the binary exponentiation in the first iteration gives total complexity .
The universal Euclidean algorithm can be written as a unified template; when applying it to a specific problem, only the implementation of the template type T needs to be changed.
Reference Implementation
// Class T implements the monoid.
// Assume that it provides a multiplication operator
// and a default constructor returning the unity in the monoid.
// Binary exponentiation.
template <typename T>
T pow(T a, int b) {
T res;
for (; b; b >>= 1) {
if (b & 1) res = res * a;
a = a * a;
}
return res;
}
// Universal Euclidean algorithm.
template <typename T>
T euclid(int a, int b, int c, int n, T U, T R) {
if (b >= c) return pow(U, b / c) * euclid(a, b % c, c, n, U, R);
if (a >= c) return euclid(a % c, b, c, n, U, pow(U, a / c) * R);
auto m = ((long long)a * n + b) / c;
if (!m) return pow(R, n);
return pow(R, (c - b - 1) / a) * U *
euclid(c, (c - b - 1) % a, a, m - 1, R, U) *
pow(R, n - (c * m - b - 1) / a);
}
The implementation of the template problem obtained using the universal Euclidean algorithm is as follows:
Template Problem Implementation (Library Checker - Sum of Floor of Linear)
#include <array>
#include <iostream>
// Switch between matrix and info merging approaches.
#define MATRIX 1
// --8<-- [start:euclidean]
// Class T implements the monoid.
// Assume that it provides a multiplication operator
// and a default constructor returning the unity in the monoid.
// Binary exponentiation.
template <typename T>
T pow(T a, int b) {
T res;
for (; b; b >>= 1) {
if (b & 1) res = res * a;
a = a * a;
}
return res;
}
// Universal Euclidean algorithm.
template <typename T>
T euclid(int a, int b, int c, int n, T U, T R) {
if (b >= c) return pow(U, b / c) * euclid(a, b % c, c, n, U, R);
if (a >= c) return euclid(a % c, b, c, n, U, pow(U, a / c) * R);
auto m = ((long long)a * n + b) / c;
if (!m) return pow(R, n);
return pow(R, (c - b - 1) / a) * U *
euclid(c, (c - b - 1) % a, a, m - 1, R, U) *
pow(R, n - (c * m - b - 1) / a);
}
// --8<-- [end:euclidean]
#if MATRIX
template <size_t N>
struct Matrix {
std::array<long long, N * N> mat;
auto loc(size_t i, size_t j) const { return mat[i * N + j]; }
auto& loc(size_t i, size_t j) { return mat[i * N + j]; }
Matrix() : mat{} {
for (size_t i = 0; i != N; ++i) {
loc(i, i) = 1;
}
}
Matrix operator*(const Matrix& rhs) const {
Matrix res;
res.mat.fill(0);
for (size_t i = 0; i != N; ++i) {
for (size_t k = 0; k != N; ++k) {
for (size_t j = 0; j != N; ++j) {
res.loc(i, j) += loc(i, k) * rhs.loc(k, j);
}
}
}
return res;
}
};
long long solve(int a, int b, int c, int n) {
if (!n) return 0;
Matrix<3> U, R;
U.loc(0, 1) = R.loc(1, 2) = 1;
auto res = euclid(a, b, c, n, U, R);
return res.loc(0, 2);
}
#else
struct Info {
long long x, y, s;
Info() : x(0), y(0), s(0) {}
Info operator*(const Info& rhs) const {
Info res;
res.x = x + rhs.x;
res.y = y + rhs.y;
res.s = s + rhs.s + rhs.x * y;
return res;
}
};
long long solve(int a, int b, int c, int n) {
if (!n) return 0;
Info U, R;
U.y = 1;
R.x = 1;
auto res = euclid(a, b, c, n, U, R);
return res.s;
}
#endif
int main() {
int t;
std::cin >> t;
for (; t; --t) {
int a, b, c, n;
std::cin >> n >> c >> a >> b;
std::cout << solve(a, b, c, n - 1) << '\n';
}
return 0;
}
Examples
Template: Euclidean-like Algorithm
For multiple queries, given positive integers , compute
Solution 2
To apply the universal Euclidean algorithm template, first separate the term for . The remaining terms can be viewed as computing for the segment with parameters . As described in the main text, there are two ways to encode an operation sequence as a monoid element.
Matrix multiplication: define the state vector as . The initial state is , and the two operations are
The final answer is given by the last three components of the vector obtained by multiplying the initial state by the product of these operation matrices.
This method has very large constants and does not pass the problem; the details are included only to aid understanding.
Combining contributions: define the contribution of an operation sequence as . The two operations are
The combined contribution satisfies
Therefore the multiplication of operations should be defined as
Although verifying this definition directly is tedious, these contribution vectors do form a monoid under this multiplication, with identity .
In the general case,
Thus, by maintaining contributions of all lower powers, sums of the general form can be computed.
#include <iostream>
template <typename T>
T pow(T a, int b) {
T res;
for (; b; b >>= 1) {
if (b & 1) res = res * a;
a = a * a;
}
return res;
}
template <typename T>
T euclid(int a, int b, int c, int n, T U, T R) {
if (b >= c) return pow(U, b / c) * euclid(a, b % c, c, n, U, R);
if (a >= c) return euclid(a % c, b, c, n, U, pow(U, a / c) * R);
auto m = ((long long)a * n + b) / c;
if (!m) return pow(R, n);
return pow(R, (c - b - 1) / a) * U *
euclid(c, (c - b - 1) % a, a, m - 1, R, U) *
pow(R, n - (c * m - b - 1) / a);
}
constexpr int M = 998244353;
struct Info {
long long x, y, s, t, u;
Info() : x(0), y(0), s(0), t(0), u(0) {}
Info operator*(const Info& rhs) const {
Info res;
res.x = (x + rhs.x) % M;
res.y = (y + rhs.y) % M;
res.s = (s + rhs.s + rhs.x * y) % M;
auto tmp = (rhs.x * (rhs.x + 1) / 2 + x * rhs.x) % M;
res.t = (t + rhs.t + x * rhs.s + tmp * y) % M;
res.u = (u + rhs.u + 2 * y * rhs.s + rhs.x * y % M * y) % M;
return res;
}
};
void solve(int a, int b, int c, int n) {
Info U, R;
U.y = 1;
R.x = 1;
auto res = euclid(a, b, c, n, U, R);
auto f = (res.s + b / c) % M;
auto g = res.t;
auto h = (res.u + (long long)(b / c) * (b / c)) % M;
std::cout << f << ' ' << h << ' ' << g << '\n';
}
int main() {
int t;
std::cin >> t;
for (; t; --t) {
int a, b, c, n;
std::cin >> n >> a >> b >> c;
solve(a, b, c, n);
}
return 0;
}
Solution 2
First handle the case in which is a perfect square; it is identical to the treatment above. Below, assume that is not a perfect square.
There are many ways to apply the universal Euclidean algorithm. For example, define a linear transformation for each operation:
Define multiplication of operations as composition of linear transformations. The final answer is the value at of the function obtained by composing the transformations for the operation sequence.
Alternatively, define the contribution of each operation sequence as . The two operations are
Define the combined contribution by
It is easy to verify that all operations form a monoid under this multiplication, with identity . The final answer is the second component of the product of all elements.
The two approaches agree: if a linear transformation is written as , composition changes its coefficients exactly according to the multiplication above, so the two monoids are isomorphic.
In this problem, the segment parameters are , where is the slope. Let the product corresponding to the operation sequence be . Then:
-
If , every in the operation sequence is preceded by at least copies of , so
-
If , swap and in the operation sequence and remove the final (that is, the original final ), so
The iteration of in this algorithm is actually computing the continued-fraction expansion of . The PQa algorithm can be used; the continued-fraction computation and the universal Euclidean iteration can be performed together.
As with the Euclidean-like algorithm, the complexity remains .
#include <algorithm>
#include <cmath>
#include <iostream>
template <typename T>
T pow(T a, int b) {
T res;
for (; b; b >>= 1) {
if (b & 1) res = res * a;
a = a * a;
}
return res;
}
struct LinearTransform {
int u, v;
LinearTransform() : u(0), v(1) {}
LinearTransform operator*(const LinearTransform& rhs) const {
LinearTransform res;
res.u = u + v * rhs.u;
res.v = v * rhs.v;
return res;
}
int eval(int x) const { return u + v * x; }
};
int solve(int n, int r) {
long double sqrt_r = sqrtl(r);
int sqr = sqrt_r;
if (r == sqr * sqr) return sqr % 2 ? (n % 2 ? -1 : 0) : n;
int P = 0, Q = 1, D = r, val = 0;
LinearTransform U, R;
U.v = -1;
R.u = 1;
while (n) {
int a = (P + sqr) / Q;
R = pow(U, a) * R;
int m = ((P + sqrt_r) / Q - a) * n;
P = a * Q - P;
Q = (D - P * P) / Q;
int rem = n - (int)(m * (P + sqrt_r) / Q);
val = pow(R, rem).eval(val);
std::swap(U, R);
n = m;
}
return val;
}
int main() {
int t;
std::cin >> t;
for (; t; --t) {
int n, r;
std::cin >> n >> r;
std::cout << solve(n, r) << '\n';
}
return 0;
}
Exercises
Template problems:
- Library Checker - Sum of Floor of Linear
- Luogu P5170 Template: Euclidean-like Algorithm
- Luogu P5171 Earthquake
- Luogu P5172 [Tsinghua Training 2014] Sum
- Luogu P4132 [BJOI2012] An Uncomputable Equation
- LOJ 138. Euclidean-like Algorithm
- LOJ 6440. Universal Euclidean Algorithm
- Luogu P5179 Fraction
- Codeforces 1182 F. Maximum Sine
Application problems:
- Luogu P4433 [COCI 2009/2010 #1] ALADIN
- AtCoder Beginner Contest 372 G - Ax + By < C
- AtCoder Beginner Contest 313 G - Redistribution of Piles
- AtCoder Beginner Contest 283 Ex - Popcount Sum
- Codeforces 1098 E. Fedya the Potter
- Codeforces 868 G. El Toll Caves
References and notes
Footnotes
-
In the usual problems, and are of the same order of magnitude, so the term can be ignored. Moreover, if one first performs a modulo-reduction step in the Euclidean-like algorithm before applying the universal algorithm, eliminating the effect of , the fast-exponentiation cost of this term can be avoided. This is because the initial form of is usually special, so its powers have a simpler form and do not need to be computed by fast exponentiation. For example, in the main text, the result of is obtained by replacing the off-diagonal in with , without using fast exponentiation. ↩