Prerequisite knowledge: Discrete Logarithm
This article discusses higher-order residues and roots of unity in modular arithmetic, and introduces algorithms for extracting roots modulo a modulus.
Higher-Order Residues
A higher-order residue in modular arithmetic can be viewed as a question of whether it is possible to extract a higher-order root modulo a modulus. It is a generalization of a quadratic residue.
-th Residue
Let the integer , and let the integer be coprime to the positive integer . If there exists an integer such that
then is, modulo , a -th residue (-th residue), and is a root of modulo of order (-th root); otherwise, is, modulo , a -th nonresidue (-th nonresidue).
In other words, modulo has a -th root if and only if modulo is a -th residue.
Properties
As with quadratic residues, one can discuss deciding -th residues, their number, and the number of -th residue classes. As with other congruence equation problems, the Chinese remainder theorem can reduce them to the case of prime-power moduli. Depending on whether a primitive root exists, this further splits into odd prime-power moduli and powers of .
The odd prime-power case is relatively simple. In fact, whenever a primitive root exists, we have the following result:
Theorem
Let the integer , and let the integer be coprime to the positive integer . Suppose a primitive root modulo exists, and let be a primitive root modulo . Let and , where is the Euler totient function. Then:
-
is, modulo , a -th residue if and only if
-
If is, modulo , a -th residue, then has, modulo , exactly distinct -th roots, of the form
-
The number of residue classes modulo of degree is , and all of them are
Proof
Since , we have . Since is a primitive root modulo , both and are congruent to powers of . Let ; then the equation is equivalent to
Here, is the discrete logarithm. By the properties of order and , this is equivalent to the congruence equation
This is a linear congruence equation in . Applying the analysis of its solution structure on that page, we know that the equation is solvable if and only if , and that its general solution has the form
This almost gives the entire theorem. The only point that needs additional explanation is the criterion . By Property 3 of order,
We already know that the equation is solvable if and only if , equivalently . By Property 2 of order, this is equivalent to the criterion above.
The case of powers of is more special. To handle it, we use a result about the structure of the reduced residue system modulo : every odd is uniquely congruent to an integer of the form , where and . This gives the following result:
Theorem
Let be an integer, let be odd, and let the positive integer with . When is odd:
- is always, modulo , a -th residue.
- has exactly one root modulo of degree .
- The number of residue classes modulo of degree is , namely all reduced residue classes.
When is even, let and . Then:
-
is, modulo , a -th residue if and only if and .
-
If is, modulo , a -th residue, then has, modulo , exactly distinct roots of degree , of the form
-
The number of residue classes modulo of degree is , and all of them are
Proof
Since , we have . Since both and are odd, the result above lets us write and . Because this representation is unique, the congruence equation is equivalent to the system of linear congruence equations
Combining this page's analysis of solutions to linear congruence equations gives the structure of the solutions to . Depending on the parity of , there are two cases:
-
When is odd, , so both linear congruence equations are solvable for every . Hence the original congruence equation is always solvable for every odd .
-
When is even, the first equation is solvable if and only if , and the second is solvable if and only if . Combining them gives all -th residue classes. Direct calculation shows that the first condition is equivalent to ; repeating the analysis for odd prime powers shows that the second is equivalent to . Combining the two gives the criterion in the theorem. The general solutions of the two linear congruence equations are also known:
Combining the two gives the general solution of the original equation.
This completely resolves the problem of deciding -th residues for different moduli. The Legendre symbol and quadratic reciprocity law for quadratic residues can also be generalized to higher-order residues, but this is difficult and requires concepts such as cyclotomic fields. In algebraic number theory, quadratic reciprocity is ultimately generalized by Artin reciprocity.
Roots of Unity
As a special case of -th roots, this section discusses the notion of -th (primitive) roots of unity. It can be viewed as the analogue in the complex field of the notion of -th roots of unity in the reduced residue system modulo , , modulo . When the modulus is suitable, using a primitive root of unity of order in place of the complex root can accelerate computations.
As in the complex case, we have the following definition:
Roots of Unity Modulo of Order
For a modulus , a root of of order is called a root of unity modulo of order (-th root of unity modulo ). In particular, if is a root of unity modulo of order and is not a root of unity modulo of any order , then is also called a primitive root of unity modulo of order (-th primitive root of unity modulo ).
Comparing this with the definition of a primitive root, we see that a primitive root is, modulo , a -th primitive root of unity, where is the Euler totient function.
When, modulo , a primitive -th root of unity exists, its algebraic properties agree with those of the primitive complex -th root of unity , so it can replace in various computations. For example, applying it to the fast Fourier transform gives the number-theoretic transform over the finite field1.
Properties
Every (primitive) root of unity of every order exists in the complex field. In number theory, however, (primitive) roots of unity do not always exist.
Properties
For a modulus , let be its Carmichael function. Then:
- For a modulus , every integer coprime to it is, modulo , a -th primitive root of unity; is the order of modulo .
- If is, modulo , a -th root of unity and is any multiple of , then is also, modulo , a -th root of unity.
- If is, modulo , a -th (primitive) root of unity, then is, modulo , a -th (primitive, respectively) root of unity.
- For ranging over the divisors of , all primitive roots of unity modulo of order form a partition of the roots of unity modulo of order . Moreover, for , the map is a bijection among the -th roots of unity and preserves this partition: it still maps -th primitive roots of unity to -th primitive roots of unity.
- Modulo , a primitive root of unity of order exists if and only if . In particular, a primitive root of unity modulo of order exists; it is called a modulo -primitive root.
- An element is, modulo , a -th root of unity if and only if and, for every prime factor , .
Proof
By the definition of order, every integer coprime to , denoted , is, modulo , a -th primitive root of unity; here is the order of modulo . Conversely, is, modulo , a -th root of unity, so and hence . Therefore, is a (primitive) root of unity modulo if and only if is coprime to . This proves Property 1.
Directly checking the definition shows that whenever , implies , which is Property 2. By the property of order,
If is, modulo , a -th primitive root of unity, then , and substituting directly into the equation above shows that is, modulo , a -th primitive root of unity. If is merely, modulo , a -th root of unity, suppose it is a -th primitive root of unity; then is, modulo , a -th primitive root of unity. Since , we have
By Property 2, is therefore, modulo , a -th root of unity. This proves Property 3.
For , Property 2 implies that every primitive root of unity modulo of order is a root of unity modulo of order . These sets are pairwise disjoint, so they form a partition. For , we also have , so for a primitive root of unity modulo of order , , is again a primitive root of unity modulo of order . Let . One can verify that and are inverse maps, so is a bijection. This proves Property 4.
By the properties of the Carmichael function, modulo , a -th primitive root of unity always exists; let it be , with . For , let . Then
Thus is a -th primitive root of unity. By the definition of the Carmichael function, the order of every is a divisor of . This proves Property 5.
Repeating almost the same proof as for the primitive-root criterion theorem gives Property 6. This criterion is actually checking that .
These properties show that, relative to the case where primitive roots exist, modulo the -primitive root plays a similar foundational role. Unlike primitive roots, powers of the -primitive root cannot generate all roots of unity modulo . Nevertheless, because -primitive roots have a non-negligible density2, if we need to find a primitive -th root of unity, we can first find a -primitive root randomly and then obtain a primitive -th root of unity by taking a power.
If modulo has a root of degree , one can use, modulo , all roots of unity of degree to generate all roots of modulo of degree .
Theorem
Let be a root of modulo of order . As ranges over all roots of unity modulo of order , ranges over all roots of modulo of order .
Proof
For two roots of modulo of order , , let . Then satisfies and is, modulo , a -th root of unity. Conversely, if is, modulo , a -th root of unity, then , so is, modulo , a -th root.
Generating all -th roots using the -th roots of unity is analogous to using the solutions of a homogeneous linear system to generate the general solution of a nonhomogeneous linear system.
The discussion so far has been general. When a primitive root exists, the structure of the roots of unity is simpler:
Theorem
For a modulus , suppose a primitive root exists modulo and is, modulo , a primitive root of unity of order . Then is, modulo , a root of unity of order if and only if it can be expressed as a power of .
Proof
Let be a primitive root modulo . Then every element coprime to can be expressed as a power of . Thus, is, modulo , a primitive root of unity of order if and only if
Similarly, is, modulo , a root of unity of order if and only if
Therefore,
By the analysis of linear congruence equations, this condition is equivalent to the equation
Having raised to the power given by this condition yields , that is, can be expressed as a power of .
This theorem shows that, when a primitive root exists, all -th roots of unity form a cyclic group, and the primitive -th roots of unity are generators of this cyclic group. We will see later that the Tonelli–Shanks algorithm exploits this fact to accelerate the discrete-logarithm part of root extraction.
Extracting Roots Modulo a Modulus
Finally, this article discusses how to find -th roots. For , there are many efficient algorithms for extracting square roots modulo a modulus. For general , however, no polynomial-time algorithm is known. This section introduces two common algorithms that find a root in and time, respectively, for degree . The Chinese remainder theorem can always reduce the problem to prime-power moduli, so this section focuses on the prime-power case.
Naive Algorithm
The preceding analysis of the properties of -th residues has already indicated a method for finding -th roots modulo a prime power. Strictly speaking, the case solved above is when the radicand is coprime to the modulus . The algorithm can be summarized as follows:
-
When is an odd prime power, let the primitive root of be . Then the equation can be converted into the linear congruence equation
Here, can be found using the BSGS algorithm, and the solutions of the linear congruence equation yield all 's -th roots .
There is also a similar approach. Again let ; by rewriting,
This becomes finding the discrete logarithm with base of . A set of particular solutions can likewise be found using BSGS. The general solution can be obtained from the solution expression above: multiply the particular solution by every root of unity of degree .
Whichever approach is used, when the primitive root is known, the complexity of finding one solution is . Since a primitive root can be found in time, the total complexity remains .
-
When and , first find the exponents in , namely . Of these two exponents, can be determined in time:
The value can be found using BSGS in time. Next, solve the system of linear congruence equations:
The general solution of this linear system is easy to obtain, and is the desired root. The complexity of finding one solution with this algorithm is still .
Of course, in the no-solution case, the criterion described above can be used to decide this quickly in time, without checking during the solving process.
The following is a reference implementation for finding a -th root modulo a prime (the code is only illustrative; its time complexity is too high to pass the problem):
Template Problem Library Checker - Kth Root (Mod), Reference Implementation
// Submission (TLE): https://judge.yosupo.jp/submission/320582
#include <algorithm>
#include <cmath>
#include <iostream>
#include <tuple>
#include <unordered_map>
#include <vector>
struct PrimePower {
int p, e, pe;
PrimePower(int p, int e, int pe) : p(p), e(e), pe(pe) {}
};
// Factorization.
auto factorize(int n) {
std::vector<PrimePower> ans;
for (int x = 2; x * x <= n; ++x) {
int e = 0, pe = 1;
for (; n % x == 0; n /= x, ++e, pe *= x);
if (e) ans.emplace_back(x, e, pe);
}
if (n > 1) ans.emplace_back(n, 1, n);
return ans;
}
// Binary exponentiation.
int pow(int a, int b, int m = 0) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = m ? (long long)res * a % m : res * a;
a = m ? (long long)a * a % m : a * a;
}
return res;
}
// Find a primitive root modulo prime.
int primitive_root(int p) {
std::vector<int> exp;
for (auto factor : factorize(p - 1)) {
exp.push_back((p - 1) / factor.p);
}
int ans = 0;
bool succ = false;
while (!succ) {
++ans;
succ = true;
for (int b : exp) {
if (pow(ans, b, p) == 1) {
succ = false;
break;
}
}
}
return ans;
}
// Discrete logarithm. (BSGS Algorithm)
int log(int g, int a, int m) {
int b = std::sqrt(m + 0.25l) + 1;
std::unordered_map<int, int> mp;
int po0 = a % m, po1 = 1;
for (int i = 0; i < b; ++i) {
mp[po0] = i;
po0 = (long long)po0 * g % m;
}
po0 = pow(g, b, m);
for (int j = 1; j <= b; ++j) {
po1 = (long long)po1 * po0 % m;
if (mp.count(po1)) return j * b - mp[po1];
}
return -1;
}
// Extended Euclidean Algorithm.
int ex_gcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1;
y = 0;
return a;
} else {
int d = ex_gcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
}
// Solves the linear congruence equation: Ax = B mod N.
// Return the least nonnegative solution and the common difference.
std::pair<int, int> solve_linear(int a, int b, int n) {
int x, y;
int d = ex_gcd(a, n, x, y);
if (b % d) return {-1, -1};
n /= d;
x = ((long long)x * (b / d) % n + n) % n;
return {x, n};
}
// Subroutine: Find a K-th root with a primitive root G known.
int calc(int g, int k, int a, int p) {
int ind = log(g, a, p);
if (ind == -1) return -1;
int y0, d;
std::tie(y0, d) = solve_linear(k, ind, p - 1);
if (y0 == -1) return -1;
return pow(g, y0, p);
}
// Find a K-th root of A modulo prime P.
int kth_roots_mod_p(int k, int a, int p) {
a %= p;
if (k == 0) return a == 1 ? 0 : -1;
if (a == 0) return 0;
return calc(primitive_root(p), k, a, p);
}
void solve() {
int k, y, p;
std::cin >> k >> y >> p;
std::cout << kth_roots_mod_p(k, y, p) << '\n';
}
int main() {
int t;
std::cin >> t;
for (; t; --t) {
solve();
}
}
Improved Tonelli–Shanks Algorithm
A suitable generalization of the Tonelli–Shanks algorithm for extracting square roots modulo a modulus can solve root extraction modulo prime powers. One relatively direct generalization is the Adleman–Manders–Miller algorithm3, but its complexity is still not good enough4. This section introduces an improved Tonelli–Shanks algorithm proposed by sugarknri, Min_25, 37zigen, and others. It can find, in time, a -th root.
The central idea of the Tonelli–Shanks algorithm is to move the discrete-logarithm computation into a group of order , thereby reducing the time complexity. Similarly, a discrete logarithm in a group of prime-power order can be computed relatively efficiently, but the complexity is . The Adleman–Manders–Miller algorithm decomposes the computation of a -th root into discrete logarithms in several groups of prime-power order, but, limited by 's largest prime factor , its complexity is still . The algorithm in this section improves the process further, avoiding discrete logarithms for large prime factors and controlling the overall complexity at .
Process
Consider, for a prime power , computing the root of of degree , that is, solving the congruence equation:
For the case , we additionally need , so that can be written as a power of . As discussed earlier, modulo , extracting a -th root can always be reduced to this case. When handling this case, namely , every occurrence of mentioned in this section should be replaced by .
First, the problem can be reduced to the case where the root degree divides . Let . By the properties of -th residues, when modulo is a -th residue, then is always, modulo , a -th root of unity. By the properties of roots of unity, for any , the map is a bijection among the -th roots of unity. Therefore, we can take
Raising both sides of the original congruence equation to the power gives
The leftmost congruence uses Euler's theorem and the following congruence relation (where ):
For the transformed problem, consider the prime factorization of :
Starting from , for each , successively extract a -th root; the result is the root of of degree , which is the root of of degree .
Finally, the problem becomes solving the following equation:
Suppose and . Let be a solution of . Since is an -th root of unity, must be a -th root of unity. Let be, modulo , a primitive -th root of unity. Then is a primitive -th root of unity, so there exists such that . Therefore, direct verification shows that
is a root of modulo of degree .
To compute , find, modulo , a -th nonresidue . By the properties above, it is enough to choose at random and check that . The density of such numbers is
Thus, in expectation, no more than random integers are needed to find one. Note that and . Therefore, if we set and , they are respectively primitive -th and -th roots of unity.
Finally, we need to compute . Clearly, we can take . Consider in base :
Compute these digits one by one. Once the first digits have been computed, we necessarily have
Thus, can be found by computing a discrete logarithm to the base . To obtain better time complexity, use BSGS. In total, discrete logarithms must be computed. If powers of are precomputed, the time complexity of one discrete-logarithm computation is , and the total time complexity is
When , the total time complexity is minimized at . After obtaining , substitute it into the expression for above to obtain a particular solution.
Time Complexity
The time complexity of this algorithm is . In this complexity discussion, we always assume that one multiplication takes time, and that exponentiation always uses Euler's theorem to reduce the exponent, so computing any individual power takes time.
First consider computing a single -th root. Finding a -th nonresidue requires checking an expected numbers, for a total time complexity of . Computing each of takes only time. When computing , each digit requires time for exponentiation, and there are digits, so the total time complexity is . As stated above, preprocessing for the discrete logarithms and the queries together take time. Since , computing a single -th root takes time. In particular, when , the time complexity can be reduced further to .
We can then consider the total time complexity of the algorithm. Computing all takes time. Next, factor ; this can be done in time using Pollard Rho. Finally, the total time for successively computing -th roots is
Since , every prime factor occurs in at least times, so we necessarily have . Therefore, the total time complexity is .
In fact, in this situation we can still achieve time without using Pollard Rho to factor the integer. It is enough to factor by trial division, enumerating only prime factors no greater than . Let the integer obtained after removing these small prime factors be . For 's prime factor , we necessarily have , where denotes, for , the exponent of in the prime factorization of the integer. Since we only need to consider
the case described above: at most one prime factor can satisfy this condition; otherwise, each would occur in at least times, making the total product exceed . To isolate this one (possibly existing) large prime factor, compute
Enumerating all possible values of shows that the exponent of in the product is always , so the result is exactly the unique large prime factor (if one exists). The remaining part can contain only prime factors satisfying , so it does not need to be factored further.
The following is a reference implementation for finding a -th root modulo a prime:
Template Problem Library Checker - Kth Root (Mod), Reference Implementation
#include <algorithm>
#include <cmath>
#include <iostream>
#include <random>
#include <tuple>
#include <unordered_map>
#include <vector>
std::mt19937 rng(std::random_device{}());
// Binary exponentiation.
int pow(int a, int b, int m = 0) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = m ? (long long)res * a % m : res * a;
a = m ? (long long)a * a % m : a * a;
}
return res;
}
// Find a P-th non-residue mod M.
int non_residue(int p, int m, int phi) {
std::uniform_int_distribution<int> dis(1, m - 1);
while (true) {
int c = dis(rng);
if (pow(c, phi / p, m) != 1) return c;
}
return -1;
}
// Euclidean Algorithm.
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
// Extended Euclidean Algorithm.
int ex_gcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1;
y = 0;
return a;
} else {
int d = ex_gcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
}
// Returns the modular inverse of A modulo M.
// Assumes that gcd(A, M) = 1, so the inverse exists.
int inv(int a, int m) {
int x, y;
ex_gcd(a, m, x, y);
return (x % m + m) % m;
}
// Subroutine: Find a P^E-th root of A mod M.
int peth_root_mod_m(int p, int e, int a, int m, int phi) {
int s = 0, r = phi, pe = pow(p, e);
for (; r % p == 0; r /= p, ++s);
int q = pe - inv(r, pe);
int ans = pow(a, ((long long)q * r + 1) / pe % phi, m);
int eta = non_residue(p, m, phi);
std::unordered_map<int, int> mp;
int zeta = pow(eta, r, m);
int xi = pow(eta, phi / p, m);
// Precompute powers for BSGS.
int B = std::sqrt((s - e) * p + 0.25l) + 1;
int pB = p / B + 1;
int po0 = pow(xi, pB, m);
for (int j = 1, po1 = 1; j <= B; ++j) {
po1 = (long long)po1 * po0 % m;
mp[po1] = j;
}
// Compute p-adic digits of h.
for (int j = 0; j < s - e; ++j) {
int err = (long long)pow(ans, pe, m) * inv(a, m) % m;
int xi_hj = pow(err, pow(p, s - e - j - 1), m);
long long hj = 0;
// BSGS query.
for (int i = 1; i <= pB; ++i) {
xi_hj = (long long)xi_hj * xi % m;
if (mp.count(xi_hj)) {
hj = mp[xi_hj] * pB - i;
break;
}
}
ans = (long long)ans * pow(zeta, phi - hj * pow(p, j) % phi, m) % m;
}
return ans;
}
// Find a K-th root of A modulo prime P.
int kth_root_mod_p(int k, int a, int p) {
a %= p;
if (k == 0) return a == 1 ? 0 : -1;
if (a == 0) return 0;
int d = gcd(k, p - 1);
if (pow(a, (p - 1) / d, p) != 1) return -1;
a = pow(a, inv(k / d, (p - 1) / d), p);
for (int dp = 2; dp * dp <= d && dp * dp * dp * dp < p; ++dp) {
if (d % dp == 0) {
int de = 0;
for (; d % dp == 0; d /= dp, ++de);
a = peth_root_mod_m(dp, de, a, p, p - 1);
}
}
if (d != 1) {
int dp = gcd(d, (p - 1) / d), de = 0;
if (dp != 1) {
for (; d % dp == 0; d /= dp, ++de);
a = peth_root_mod_m(dp, de, a, p, p - 1);
}
if (d != 1) a = peth_root_mod_m(d, 1, a, p, p - 1);
}
return a;
}
void solve() {
int k, y, p;
std::cin >> k >> y >> p;
std::cout << kth_root_mod_p(k, y, p) << '\n';
}
int main() {
int t;
std::cin >> t;
for (; t; --t) {
solve();
}
}
Handling the General Case
Consider the general case, still with modulus , a prime power , but now . If , then
are all solutions of the original equation. Next, consider the case . Let with . Then, writing with , we have
Since , this holds if and only if and . The first equation has a solution if and only if , in which case ; the second equation has already been solved. Note that the modulus of the general solution of the second equation differs from that of the general solution of the original equation, so each solution of the second equation corresponds to several solutions of the original equation:
The following is a reference implementation for finding all -th roots modulo an arbitrary modulus:
Template Problem Luogu P5668 [Template] N-th Residue, Reference Code
Naive Algorithm
#include <algorithm>
#include <cmath>
#include <iostream>
#include <tuple>
#include <unordered_map>
#include <vector>
struct PrimePower {
int p, e, pe;
PrimePower(int p, int e, int pe) : p(p), e(e), pe(pe) {}
};
// Factorization.
auto factorize(int n) {
std::vector<PrimePower> ans;
for (int x = 2; x * x <= n; ++x) {
int e = 0, pe = 1;
for (; n % x == 0; n /= x, ++e, pe *= x);
if (e) ans.emplace_back(x, e, pe);
}
if (n > 1) ans.emplace_back(n, 1, n);
return ans;
}
// Binary exponentiation.
int pow(int a, int b, int m = 0) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = m ? (long long)res * a % m : res * a;
a = m ? (long long)a * a % m : a * a;
}
return res;
}
// Find a primitive root modulo odd prime power.
int primitive_root(PrimePower pp) {
std::vector<int> exp;
int phi = pp.pe / pp.p * (pp.p - 1);
for (auto factor : factorize(pp.p - 1)) {
exp.push_back(phi / factor.p);
}
if (pp.e != 1) exp.push_back(phi / pp.p);
int ans = 0;
bool succ = false;
while (!succ) {
++ans;
succ = true;
for (int b : exp) {
if (pow(ans, b, pp.pe) == 1) {
succ = false;
break;
}
}
}
return ans;
}
// Discrete logarithm. (BSGS Algorithm)
int log(int g, int a, int m) {
int b = std::sqrt(m + 0.25l) + 1;
std::unordered_map<int, int> mp;
int po0 = a % m, po1 = 1;
for (int i = 0; i < b; ++i) {
mp[po0] = i;
po0 = (long long)po0 * g % m;
}
po0 = pow(g, b, m);
for (int j = 1; j <= b; ++j) {
po1 = (long long)po1 * po0 % m;
if (mp.count(po1)) return j * b - mp[po1];
}
return -1;
}
// Extended Euclidean Algorithm.
int ex_gcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1;
y = 0;
return a;
} else {
int d = ex_gcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
}
// Returns the modular inverse of A modulo M.
// Assumes that gcd(A, M) = 1, so the inverse exists.
int inv(int a, int m) {
int x, y;
ex_gcd(a, m, x, y);
return (x % m + m) % m;
}
// Solves the linear congruence equation: Ax = B mod N.
// Return the least nonnegative solution and the common difference.
std::pair<int, int> solve_linear(int a, int b, int n) {
int x, y;
int d = ex_gcd(a, n, x, y);
if (b % d) return {-1, -1};
n /= d;
x = ((long long)x * (b / d) % n + n) % n;
return {x, n};
}
// Subroutine: Find all the K-th roots with a primitive root G known.
std::vector<int> calc(int g, int k, int a, int p, int pe) {
int ind = log(g, a, pe);
if (ind == -1) return {};
int mm = p == 2 ? pe / 4 : pe / p * (p - 1);
int y0, d;
std::tie(y0, d) = solve_linear(k, ind, mm);
if (y0 == -1) return {};
int ans = pow(g, y0, pe), po = pow(g, d, pe);
std::vector<int> res(mm / d);
for (auto& x : res) {
x = ans;
ans = (long long)ans * po % pe;
}
return res;
}
// Find all the K-th roots of A modulo prime power P^E.
std::vector<int> kth_roots_mod_pe(int k, int a, PrimePower pp) {
int p = pp.p, e = pp.e, pe = pp.pe;
a %= pe;
if (a == 0) {
int d = pow(p, (e - 1) / k + 1);
std::vector<int> res(pe / d);
for (int i = 0; i < pe / d; ++i) {
res[i] = i * d;
}
return res;
}
int s = 0;
for (; a % p == 0; a /= p, ++s);
if (s % k) return {};
int psk = pow(p, s / k), pss = pow(p, s - s / k), pes = pow(p, e - s);
std::vector<int> res;
if (p != 2) {
int g = primitive_root(PrimePower(p, e - s, pes));
res = calc(g, k, a, p, pes);
} else if (pes == 2) {
res.push_back(a);
} else if (k & 1) {
int z = a % 4 == 3;
a = z ? pes - a : a;
res = calc(5, k, a, p, pes);
if (z) {
for (auto& x : res) x = pes - x;
}
} else {
if (a % 4 == 3) return {};
res = calc(5, k, a, p, pes);
int m = res.size();
res.reserve(m * 2);
for (int i = 0; i < m; ++i) {
res.push_back(pes - res[i]);
}
}
int m = res.size();
res.reserve(m * pss);
for (int j = 1; j < pss; ++j) {
for (int i = 0; i < m; ++i) {
res.push_back(res.end()[-m] + pes);
}
}
for (auto& x : res) x *= psk;
return res;
}
// Find all the K-th roots of A modulo positive integer M.
std::vector<int> kth_roots_mod_m(int k, int a, int m) {
auto factors = factorize(m);
int m0 = 0;
std::vector<std::vector<int>> sols;
for (const auto& pp : factors) {
sols.push_back(kth_roots_mod_pe(k, a, pp));
if (sols.back().empty()) return {};
}
std::vector<int> ans;
for (int i = 0; i < (int)factors.size(); ++i) {
auto pp = factors[i];
if (!i) {
m0 = pp.pe;
ans = sols[i];
} else {
long long m1 = pp.pe * inv(pp.pe, m0);
long long m2 = m0 * inv(m0, pp.pe);
m0 *= pp.pe;
std::vector<int> _ans;
_ans.reserve(ans.size() * sols[i].size());
for (auto x : ans) {
for (auto y : sols[i]) {
_ans.push_back((m1 * x + m2 * y) % m0);
}
}
ans.swap(_ans);
}
}
return ans;
}
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
auto ans = kth_roots_mod_m(n, k, m);
if (ans.empty()) {
std::cout << 0 << '\n';
return;
}
std::cout << ans.size() << '\n';
std::sort(ans.begin(), ans.end());
for (auto x : ans) std::cout << x << ' ';
std::cout << '\n';
}
int main() {
int t;
std::cin >> t;
for (; t; --t) {
solve();
}
}
Improved Tonelli–Shanks algorithm
#include <algorithm>
#include <cmath>
#include <iostream>
#include <random>
#include <tuple>
#include <unordered_map>
#include <vector>
std::mt19937 rng(std::random_device{}());
struct PrimePower {
int p, e, pe;
PrimePower(int p, int e, int pe) : p(p), e(e), pe(pe) {}
};
// Factorization.
auto factorize(int n) {
std::vector<PrimePower> ans;
for (int x = 2; x * x <= n; ++x) {
int e = 0, pe = 1;
for (; n % x == 0; n /= x, ++e, pe *= x);
if (e) ans.emplace_back(x, e, pe);
}
if (n > 1) ans.emplace_back(n, 1, n);
return ans;
}
// Binary exponentiation.
int pow(int a, int b, int m = 0) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = m ? (long long)res * a % m : res * a;
a = m ? (long long)a * a % m : a * a;
}
return res;
}
// Find a primitive root modulo odd prime power.
int primitive_root(PrimePower pp) {
std::vector<int> exp;
int phi = pp.pe / pp.p * (pp.p - 1);
for (auto factor : factorize(pp.p - 1)) {
exp.push_back(phi / factor.p);
}
if (pp.e != 1) exp.push_back(phi / pp.p);
int ans = 0;
bool succ = false;
while (!succ) {
++ans;
succ = true;
for (int b : exp) {
if (pow(ans, b, pp.pe) == 1) {
succ = false;
break;
}
}
}
return ans;
}
// Euclidean Algorithm.
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
// Extended Euclidean Algorithm.
int ex_gcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1;
y = 0;
return a;
} else {
int d = ex_gcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
}
// Returns the modular inverse of A modulo M.
// Assumes that gcd(A, M) = 1, so the inverse exists.
int inv(int a, int m) {
int x, y;
ex_gcd(a, m, x, y);
return (x % m + m) % m;
}
// Find a P-th non-residue mod M.
int non_residue(int p, int m, int phi) {
std::uniform_int_distribution<int> dis(1, m - 1);
while (true) {
int c = dis(rng);
if (gcd(c, m) == 1 && pow(c, phi / p, m) != 1) return c;
}
return -1;
}
// Subroutine: Find a P^E-th root of A mod M.
int peth_root_mod_m(int p, int e, int a, int m, int phi) {
if (m == 2) return 1;
int s = 0, r = phi, pe = pow(p, e);
for (; r % p == 0; r /= p, ++s);
int q = pe - inv(r, pe);
int ans = pow(a, ((long long)q * r + 1) / pe % phi, m);
int eta = non_residue(p, m, phi);
std::unordered_map<int, int> mp;
int zeta = pow(eta, r, m);
int xi = pow(eta, phi / p, m);
// Precompute powers for BSGS.
int B = std::sqrt((s - e) * p + 0.25l) + 1;
int pB = pe / B + 1;
int po0 = pow(xi, pB, m);
for (int j = 1, po1 = 1; j <= B; ++j) {
po1 = (long long)po1 * po0 % m;
mp[po1] = j;
}
// Compute p-adic digits of h.
for (int j = 0; j < s - e; ++j) {
int err = (long long)pow(ans, pe, m) * inv(a, m) % m;
int xi_hj = pow(err, pow(p, s - e - j - 1), m);
long long hj = 0;
// BSGS query.
for (int i = 1; i <= pB; ++i) {
xi_hj = (long long)xi_hj * xi % m;
if (mp.count(xi_hj)) {
hj = mp[xi_hj] * pB - i;
break;
}
}
ans = (long long)ans * pow(zeta, phi - hj * pow(p, j) % phi, m) % m;
}
return ans;
}
// Find a K-th root of A modulo prime P^E.
int kth_root_mod_pe(int k, int a, int pe, int phi) {
a %= pe;
if (k == 0) return a == 1 ? 0 : -1;
if (a == 0) return 0;
int d = gcd(k, phi);
if (pow(a, phi / d, pe) != 1) return -1;
a = pow(a, inv(k / d, phi / d), pe);
for (int dp = 2; dp * dp <= d && dp * dp * dp * dp < pe; ++dp) {
if (d % dp == 0) {
int de = 0;
for (; d % dp == 0; d /= dp, ++de);
a = peth_root_mod_m(dp, de, a, pe, phi);
}
}
if (d != 1) {
int dp = gcd(d, phi / d), de = 0;
if (dp != 1) {
for (; d % dp == 0; d /= dp, ++de);
a = peth_root_mod_m(dp, de, a, pe, phi);
}
if (d != 1) a = peth_root_mod_m(d, 1, a, pe, phi);
}
return a;
}
// Subroutine: Find all the K-th roots with a primitive root G known.
std::vector<int> calc(int g, int k, int a, int p, int pe) {
int mm = p == 2 ? pe / 4 : pe / p * (p - 1);
int ans = kth_root_mod_pe(k, a, pe, mm);
if (ans == -1) return {};
int d = mm / gcd(k, mm);
int po = pow(g, d, pe);
std::vector<int> res(mm / d);
for (auto& x : res) {
x = ans;
ans = (long long)ans * po % pe;
}
return res;
}
// Find all the K-th roots of A modulo prime power P^E.
std::vector<int> kth_roots_mod_pe(int k, int a, PrimePower pp) {
int p = pp.p, e = pp.e, pe = pp.pe;
a %= pe;
if (a == 0) {
int d = pow(p, (e - 1) / k + 1);
std::vector<int> res(pe / d);
for (int i = 0; i < pe / d; ++i) {
res[i] = i * d;
}
return res;
}
int s = 0;
for (; a % p == 0; a /= p, ++s);
if (s % k) return {};
int psk = pow(p, s / k), pss = pow(p, s - s / k), pes = pow(p, e - s);
std::vector<int> res;
if (p != 2) {
int g = primitive_root(PrimePower(p, e - s, pes));
res = calc(g, k, a, p, pes);
} else if (pes == 2) {
res.push_back(a);
} else if (k & 1) {
int z = a % 4 == 3;
a = z ? pes - a : a;
res = calc(5, k, a, p, pes);
if (z) {
for (auto& x : res) x = pes - x;
}
} else {
if (a % 4 == 3) return {};
res = calc(5, k, a, p, pes);
int m = res.size();
res.reserve(m * 2);
for (int i = 0; i < m; ++i) {
res.push_back(pes - res[i]);
}
}
int m = res.size();
res.reserve(m * pss);
for (int j = 1; j < pss; ++j) {
for (int i = 0; i < m; ++i) {
res.push_back(res.end()[-m] + pes);
}
}
for (auto& x : res) x *= psk;
return res;
}
// Find all the K-th roots of A modulo positive integer M.
std::vector<int> kth_roots_mod_m(int k, int a, int m) {
auto factors = factorize(m);
int m0 = 0;
std::vector<std::vector<int>> sols;
for (const auto& pp : factors) {
sols.push_back(kth_roots_mod_pe(k, a, pp));
if (sols.back().empty()) return {};
}
std::vector<int> ans;
for (int i = 0; i < (int)factors.size(); ++i) {
auto pp = factors[i];
if (!i) {
m0 = pp.pe;
ans = sols[i];
} else {
long long m1 = pp.pe * inv(pp.pe, m0);
long long m2 = m0 * inv(m0, pp.pe);
m0 *= pp.pe;
std::vector<int> _ans;
_ans.reserve(ans.size() * sols[i].size());
for (auto x : ans) {
for (auto y : sols[i]) {
_ans.push_back((m1 * x + m2 * y) % m0);
}
}
ans.swap(_ans);
}
}
return ans;
}
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
auto ans = kth_roots_mod_m(n, k, m);
if (ans.empty()) {
std::cout << 0 << '\n';
return;
}
std::cout << ans.size() << '\n';
std::sort(ans.begin(), ans.end());
for (auto x : ans) std::cout << x << ' ';
std::cout << '\n';
}
int main() {
int t;
std::cin >> t;
for (; t; --t) {
solve();
}
}
References and notes
- Feng Keqin, Elementary Number Theory and Its Applications.
- Root of unity modulo n - Wikipedia
- No.981 General k-th Root, by 37zigen
Footnotes
-
The modulus need not be prime. Any primitive -th root of unity modulo can be used for a fast number-theoretic transform modulo . In practice, however, the required is usually large, which means every prime factor of has the form . Each such factor is therefore large, and the composite-modulus case is much less common than the prime-modulus case. ↩
-
By the counting result for primitive roots, the number of -primitive roots is exactly , where and are Euler's and Carmichael's functions. For almost every integer , , while there is a constant such that for . Hence, for almost every , . The term absorbs the contribution from . Thus a -primitive root can be found in expected time . Estimates for Euler's function can be found in Rosser, J. Barkley, and Lowell Schoenfeld, “Approximate formulas for some functions of prime numbers,” Illinois Journal of Mathematics 6, no. 1 (1962): 64–94. Estimates for Carmichael's function can be found in Erdos, Paul, Carl Pomerance, and Eric Schmutz, “Carmichael’s lambda function,” Acta Arith 58, no. 4 (1991): 363–385. ↩
-
The original paper is Adleman, Leonard, Kenneth Manders, and Gary Miller, “On taking roots in finite fields,” in 18th Annual Symposium on Foundations of Computer Science (SFCS 1977), pp. 175–178, IEEE Computer Society, 1977. A more readable introduction is Cao, Zhengjun, Qian Sha, and Xiao Fan, “Adleman–Manders–Miller root extraction method revisited,” in International Conference on Information Security and Cryptology, pp. 77–85, Springer, Berlin and Heidelberg, 2011. ↩
-
This algorithm requires to be prime. In the worst case it must find a -th root of modulo , where is the largest prime factor of . The inputs and are fixed, and the algorithm must also determine a discrete logarithm of modulo to a -th primitive root of unity; even with BSGS it takes time. Fouvry's paper, “Theoreme de Brun–Titchmarsh; application au theoreme de Fermat,” Inventiones mathematicae 79, no. 2 (1985): 383–407, shows that a positive-density set of primes has largest prime factor in . Thus this algorithm has complexity at least , worse than the improved Tonelli–Shanks algorithm described here. ↩