Number Theory
Number Theory
Number Theory
Euler function
Definition
Euler function
(sometimes denoted
or
) - the number of properties to prime
to . In other words, the quantity of such properties in a segment
, the greatest common divisor
of which is to unity.
The first few values of this function ( A000010 in OEIS encyclopedia ):
Properties
The following three simple properties of the Euler - enough to learn how to calculate it for any
number:
If
- a prime, then
If
(This follows from the Chinese remainder theorem . Consider an arbitrary number
. denote
and the remnants of the division at and , respectively. then coprime if and only if is prime
to and separately, or, equivalently, a one- simply and relatively prime to . Applying the
Chinese remainder theorem, we see that any pair of numbers and the number of one-to-one
correspondence , which completes the proof.)
From here you can get the Euler function for all
prime factors):
if
(Where all
- common), then
into
Implementation
The simplest code that computes the Euler function, factoring in the number of elementary
method
:
int phi (int n) {
int result = n;
for (int i=2; i*i<=n; ++i)
if (n % i == 0) {
while (n % i == 0)
n /= i;
result -= result / i;
}
if (n > 1)
result -= result / n;
return result;
}
The key place for the calculation of the Euler function - is to find the factorization of the number
can be done in a time much smaller
: see. Efficient algorithms for factorization .
. It
where and
Euler's theorem occurs quite often in practical applications, for example, see. inverse element in the
modulo .
2.Binary exponentiation
Binary (binary) exponentiation - is a te chnique that allows you to build any number
the
multiplications (instead of the usual approach of multiplications).
of th degree of
Moreover, the technique described here is applicable to any of the associative operation, and not
only to the multiplication number. Recall operation is called associative if for any
executed:
Algorithm
Note that for any number and an even number of
associativity of multiplication):
It is the main method in the binary exponentiation. Indeed, for even we have shown how, by spending only
one multiplication, one can reduce the problem to less than half the power.
It remains to understand what to do if the degree is odd . Here we do is very simple: move on to the
power that will have an even:
Implementation
A simple recursive implementation:
int binpow (int a, int n) {
if (n == 0)
return 1;
if (n % 2 == 1)
return binpow (a, n-1) * a;
else {
int b = binpow (a, n/2);
return b * b;
}
}
Non-recursive implementation, also optimized (division by 2 replaced bit operations):
int binpow (int a, int n) {
int res = 1;
while (n)
if (n & 1) {
res *= a;
--n;
}
else {
a *= a;
n >>= 1;
}
return res;
}
This implementation is still somewhat simplified by noting that the construction of the square is
always performed, regardless of whether the condition is odd worked or not:
int binpow (int a, int n) {
int res = 1;
while (n) {
if (n & 1)
res *= a;
a *= a;
n >>= 1;
}
return res;
}
Finally, it is worth noting that the binary exponentiation already implemented by Java, but only for a
class of long arithmetic BigInteger (pow function in this class works under binary exponentiation
algorithm).
. Required to calculate
where
Decision . More detail the decision described in the article on the Fibonacci sequence . Here we only
briefly we present the essence of the decision.
The basic idea is as follows. Calculation of the next Fibonacci number is based on the knowledge of
the previous two Fibonacci numbers, namely, each successive Fibonacci number is the sum of the
previous two. This means that we can construct a matrix
that will fit this transformation: as the
two Fibonacci numbers and
calculate the next number, ie pass to the pair
,
. For
example, applying this transformation to a couple times and we get a couple
and
. Thus, elevating the matrix of this transformation in the power of th, we thus find the required
time for
what we required.
Shift operation - it just adds to all the coordinates of the unit, multiplied by some constant.
Zoom operation - it multiplies each coordinate by a constant.
Operation rotational axis - it can be represented as follows: new received coordinates can be written as a linear
combination of the old.
(We will not specify how this is done. Example, you can submit it for simplicity as a combination of
five-dimensional rotations: first, in the planes
, and
so that the axis of rotation coincides
with the positive direction of the axis
, then the desired rotation around an axis in the plane
,
then reverse rotations in planes
and
so that the axis of rotation back to its starting
position.)
Easy to see that each of these transformations - a recalculation of coordinates on linear
formulas. Thus, any such transformation can be written in matrix form
:
which, when multiplied (left) to the line of the old coordinates and constant unit gives a string of new
coordinates and constants units:
(Why it took to enter a dummy fourth coordinate that is always equal to one? Without this we would
have to implement the shift operation: after the shift - it is just adding to the coordinates of the unit,
multiplied by certain coefficients. Without dummy units we would be able to implement only linear
combinations of the coordinates themselves, and add thereto predetermined constant - could not.)
Now the solution of the problem becomes almost trivial. Times each elementary operation described
by the matrix, then the process described by the product of these matrices, and the operation of a
cyclic repetition - the erection of this matrix to a power. Thus, during the time
we
can predposchitat matrix
that describes all the changes, and then
just multiply each point on the matrix - thus, we will answer all questions in time
.
(Note. In the same article, and the other is considered a variation of this problem: when the graph is
weighted, and you want to find the path of minimum weight, containing exactly the edges. As shown
in this paper, this problem is also solved by using binary exponentiation of the adjacency matrix of the
graph, but instead of the usual operation of multiplication of two matrices should be used modified:
instead of multiplying the amount taken, and instead of summing - Take a minimum.)
Assume that the numbers can be quite large: so that the numbers themselves are placed in a built-in
data types, but their direct product
- no longer exists (note that we also need to sum numbers
placed in a built-in data type). Accordingly, the task is to find the desired value
,
without the aid of a long arithmetic .
The decision is as follows. We simply apply the binary exponentiation algorithm described above,
but instead of multiplication, we will make the addition. In other words, the multiplication of two
numbers, we have reduced to
the operations of addition and multiplication by two (which is
also, in fact, is the addition).
(Note. This problem can be solved in a different way , by resorting to assistance operations with
floating-point numbers. Namely, to calculate the floating point expression
, and round it to the
nearest integer. So we find an approximate quotient. Subtracting from his works
(ignoring
overflow), we are likely to get a relatively small number, which can be taken modulo - and return it
as an answer. This solution looks pretty unreliable, but it is very fast, and very briefly, is realized.)
[Difficulty: Medium]
Given two non-negative integers and . Required to find their greatest common divisor, ie the largest
number that divides both a and . English "greatest common divisor" is spelled "greatest common
divisor", and its common designation is
:
When it is of the numbers is zero, and the other is non-zero, their greatest common divisor, by
definition, it will be the second number. When the two numbers are equal to zero, the result is
undefined (any suitable infinite number), we put in this case, the greatest common divisor of
zero. Therefore, we can speak of such a rule: if one of the numbers is zero, the greatest common
divisor equal to the second number.
Euclid's algorithm , discussed below, solves the problem of finding the greatest common divisor of
two numbers and for
.
This algorithm was first described in the book of Euclid's "Elements" (around 300 BC), although it is
possible, this algorithm has an earlier origin.
Algorithm
The algorithm itself is very simple and is described by the following formula:
Implementation
int gcd (int a, int b) {
if (b == 0)
return a;
else
return gcd (b, a % b);
}
Using the ternary conditional operator C ++, the algorithm can be written even shorter:
int gcd (int a, int b) {
return b ? gcd (b, a % b) : a;
}
Finally, we present an algorithm and a non-recursive form:
int gcd (int a, int b) {
while (b) {
a %= b;
swap (a, b);
}
return a;
}
Proof of correctness
First, note that at each iteration of the Euclidean algorithm its second argument is strictly decreasing,
therefore, since it is non-negative, then the Euclidean algorithm always terminates .
To prove the correctness , we need to show that
any
.
for
We show that the quantity on the left-hand side is divided by the present on the right and the righthand - is divided into standing on the left. Obviously, this would mean that the left and right sides of
the same, and that proves the correctness of Euclid's algorithm.
Denote
. Then, by definition,
and
We now use the following simple fact: if for any three numbers
executed and:
. In our situation, we obtain:
performed:
and
then
we obtain:
So, we spent half of the proof: show that the left-right divide. The second half of the proof is similar.
Working time
Time of the algorithm is evaluated Lame theorem which establishes a surprising connection of the
Euclidean algorithm and the Fibonacci sequence:
If
calls.
and
for some
recursive
Moreover, it can be shown that the upper bound of this theorem - optimal. When
it will be done
recursive call. In other words, successive Fibonacci numbers - the worst
input for Euclid's algorithm.
Given that the Fibonacci numbers grow exponentially (as a constant in power
Euclidean algorithm runs in
multiplication operations.
Thus, the calculation of the NOC can also be done using the Euclidean algorithm, with the same
asymptotic behavior:
int lcm (int a, int b) {
return a / gcd (a, b) * b;
}
(Here is advantageous first divided into
overflows in some cases)
Literature
Thomas Cormen, Charles Leiserson, Ronald Rivest, Clifford Stein. Algorithms: Design and
Analysis [2005]
4.Sieve of Eratosthenes
Sieve of Eratosthenes - an algorithm for finding all prime numbers in the interval
the
operations.
for
Implementation
Immediately we present implementation of the algorithm:
int n;
vector<char> prime (n+1, true);
prime[0] = prime[1] = false;
for (int i=2; i<=n; ++i)
if (prime[i])
if (i * 1ll * i <= n)
for (int j=i*i; j<=n; j+=i)
prime[j] = false;
This code first checks all numbers except zero and one, as simple, and then begins the process of
sifting composite numbers. To do this, we loop through all the numbers from before , and if the
current number is simple, then mark all the numbers that are multiples of him as a constituent.
At the same time we are beginning to go from , as all lower multiples , be sure to have a prime
divisor less , which means that they have already been screened before.(But as can easily
overwhelm type
in the code before the second nested loop is an additional check using the
type
.)
With this implementation, the algorithm consumes
memory (obviously) and performs
the
action (this is proved in the next section).
Asymptotics
We prove that the asymptotic behavior of the algorithm is
Let us recall here two known facts: that the number of primes less than or equal to
equal
, and that flashover prime approximately equal
statement). Then the sum can be written as follows:
Here we have identified the first prime of the sum, since the
that will lead to division by zero.
approximately
approximation of the
turn ,
We now estimate a sum by the integral of the same function on from before
(we can produce
such an approximation, because, in fact, refers to the sum of the integral as its approximation by the
formula of rectangles):
Now, returning to the initial sum, we obtain the approximate its assessment:
QED.
More rigorous proof (and giving a more accurate estimate, up to constant factors) can be found in the
book of Hardy and Wright "An Introduction to the Theory of Numbers" (p. 349).
The following are the methods to both reduce the number of operations performed, as well as
significantly reduce the memory consumption.
Block sieve
Optimization of "simple screening to the root" implies that there is no need to store all the time the
whole array
. To perform screening sufficient to store only the simple to the root of ,
ie
, the remainder of the array
to build a block by block, keeping the current
time, only one block.
Let - a constant determining the size of the block, then only will
(
) contains a number in the interval
. We processed blocks of the
queue, i.e. for each th block will go through all the simple (as before
) and perform their
screening only within the current block. Carefully handle should first block - firstly, from
simple
do not have to remove themselves, and secondly, the number and should be
marked as not particularly easy. When processing the last block should also not forget the fact that
the latter desired number is not necessarily the end of the block.
We present the implementation of the sieve block. The program reads the number
number of simple to :
and finds a
n;
>> n;
nsqrt = (int) sqrt (n + .0);
(int i=2; i<=nsqrt; ++i)
if (!nprime[i]) {
primes[cnt++] = i;
if (i * 1ll * i <= nsqrt)
for (int j=i*i; j<=nsqrt; j+=i)
nprime[j] = true;
}
int result = 0;
for (int k=0, maxk=n/S; k<=maxk; ++k) {
memset (bl, 0, sizeof bl);
int start = k * S;
for (int i=0; i<cnt; ++i) {
int start_idx = (start + primes[i] - 1) / primes[i];
int j = max(start_idx,2) * primes[i] - start;
for (; j<S; j+=primes[i])
bl[j] = true;
}
if (k == 0)
bl[0] = bl[1] = true;
for (int i=0; i<S && start+i<=n; ++i)
if (!bl[i])
++result;
}
cout << result;
}
Asymptotic behavior of the sieve block is the same as usual and the sieve of Eratosthenes (unless, of
course, the size of the blocks is not very small), but the amount of memory used will be reduced
to
decrease and "walk" from memory. On the other hand, for each block for each of the
simple
division is performed, which will strongly affect in a smaller unit. Consequently, the
choice of the constant need to keep a balance.
Experiments show that the best performance is achieved when the value is about
to
Ie he finds the coefficients with which the GCD of two numbers expressed in terms of the numbers
themselves.
Algorithm
Make the calculation of these coefficients in the Euclidean algorithm is simple enough to derive
formulas by which they change from pair
to pair
(percent sign denotes the modulo).
Thus, suppose we have found a solution
and
obtain:
Comparing this with the original expression of the unknown , and we obtain the required
expression:
Implementation
int gcd (int a, int b, int & x, int & y) {
if (a == 0) {
x = 0; y = 1;
return b;
}
int x1, y1;
int d = gcd (b%a, a, x1, y1);
x = y1 - (b / a) * x1;
y = x1;
return d;
}
This is a recursive function, which still returns the GCD of the numbers and , but apart from that as desired coefficients and as a function parameter, passed by reference.
Base of recursion - the case
. Then GCD equal , and, obviously, the desired ratio and are
and respectively. In other cases, the usual solution is working, and the coefficients are converted by
the above formulas.
Advanced Euclidean algorithm in this implementation works correctly even for negative numbers.
Literature
Thomas Cormen, Charles Leiserson, Ronald Rivest, Clifford Stein. Algorithms: Design and
Analysis [2005]
6.Fibonacci numbers
Definition
The Fibonacci sequence is defined as follows:
History
These numbers introduced in 1202 by Leonardo Fibonacci (Leonardo Fibonacci) (also known as
Leonardo of Pisa (Leonardo Pisano)). However, thanks to the 19th century mathematician Luca
(Lucas) the name of "Fibonacci numbers" became common.
However, the Indian mathematicians mentioned number of this sequence even earlier: Gopal
(Gopala) until 1135, Hemachandra (Hemachandra) - in 1150
pair? ". The solution to this problem and will be the number of sequences, now called in his
honor. However, the situation described by Fibonacci - more mind game than real nature.
Indian mathematicians Gopala and Hemachandra mentioned this sequence number in relation to the
number of rhythmic patterns resulting from the alternation of long and short syllables in verse, or the
strong and weak beats in the music. The number of such patterns having generally shares
power
.
Fibonacci numbers appear in the work of Kepler in 1611, which reflected on the numbers found in
nature (the work "On the hexagonal flakes").
An interesting example of plants - yarrow, in which the number of stems (and hence the flowers) is
always a Fibonacci number. The reason for this is simple: as originally with a single stem, this stem is
then divided by two, and then branches from the main stalk another, then the first two stems branch
again, and then all the stems, but the last two, branch, and so on. Thus, each stalk after his
appearance "skips" one branch, and then begins to divide at each level of branches, which results in
a Fibonacci number.
Generally speaking, many colors (eg, lilies), the number of petals is a way or another Fibonacci
number.
Also botanically known phenomenon of 'phyllotaxis'. As an example, the location of sunflower seeds:
if you look down on their location, you can see two simultaneous series of spirals (like overlapping):
some are twisted clockwise, the other - against. It turns out that the number of these spirals is roughly
equal to two consecutive Fibonacci numbers: 34 and 55 or 89 and 144 Similar facts are true for some
other colors, as well as pine cones, broccoli, pineapple, etc.
For many plants (according to some sources, 90% of them) are true and an interesting fact. Consider
any sheet, and will descend downwardly until, until we reach the sheet disposed on the stem in the
same way (i.e., directed exactly in the same way). Along the way, we assume that all the leaves that
fall to us (ie, located at an altitude between the start and end sheet), but arranged
differently. Numbering them, we will gradually make the turns around the stem (as the leaves are
arranged on the stem in a spiral).Depending on whether the windings perform clockwise or
counterclockwise will receive a different number of turns. But it turns out that the number of turns,
committed us clockwise the number of turns, committed anti-clockwise, and the number of leaves
encountered form 3 consecutive Fibonacci numbers.
However, it should be noted that there are plants for which The above calculations give the number of
all other sequences, so you can not say that the phenomenon of phyllotaxis is the law - it is rather
entertaining trend.
Properties
Fibonacci numbers have many interesting mathematical properties.
Here are just a few of them:
Rule "addition":
always divisible
follows:
if
fold
, the
fold
GCD-equality:
With respect to the Euclidean algorithm Fibonacci numbers have the remarkable property that they are the
worst input data for this algorithm (see. "Theorem Lame" inEuclid's algorithm ).
where
,
Fibonacci numbers).
It follows that any number can be written uniquely in the Fibonacci value , for example:
This formula is easy to prove by induction, but you can bring it by the concept of forming or using the
solution of the functional equation.
Immediately you will notice that the second term is always less than 1 in absolute value, and
furthermore, decreases very rapidly (exponentially). This implies that the value of the first term gives
the "almost" value
. This can be written simply as:
obtain
Thus, to find
of power
Remembering that the construction of the matrix in the degree of th can be accomplished
(see. binary exponentiation ), it turns out that the number of Fibonacci retracement can be easily
calculated for
c using only integer arithmetic.
Since modulo may be only different pairs, there exists the sequence of at least two of the same
pair. This already means that the sequence is periodic.
We now choose among all such identical pairs of two identical pairs with the lowest numbers. Let this
pair with some of the rooms
and
. We will prove that
. Indeed,
otherwise they will have to previous couple
and
that, by the property of the
Fibonacci numbers, will also be equal to each other.However, this contradicts the fact that we chose
the matching pairs with the lowest numbers, as required.
Literature
Ronald Graham, Donald Knuth, and Oren Patashnik. Concrete Mathematics [1998]
It is clear that for the zero return item does not exist ever; for the remaining elements of the inverse
can exist or not. It is argued that the inverse exists only for those elements that are relatively
prime to the modulus .
Consider the following two ways of finding the inverse element employed, provided that it exists.
Finally, we consider an algorithm which allows you to find backlinks to all numbers modulo some
linear time.
, we get:
else {
x = (x % m + m) % m;
cout << x;
}
Asymptotic behavior of the solutions obtained
, we obtain:
Thus, we have obtained the formula for the direct calculation of the inverse. For practical applications
typically use an efficient algorithm for binary exponentiation , which in this case will bring about for
exponentiation
.
This method seems to be a little bit easier as described in the previous paragraph, but it requires
knowledge of the values of the Euler function that actually requires the factorization of the module
which can sometimes be very difficult.
If the factorization of the number is known, then this method also works for the asymptotic
behavior
.
Applying the algorithms described above, we get a solution with the asymptotic
behavior
. Here we present a simple solution to the asymptotic behavior
The decision is as follows. We denote
the
true identity:
.
. Then
r[1] = 1;
for (int i=2; i<m; ++i)
r[i] = (m - (m/i) * r[m%i] % m) % m;
The proof of this solution is a chain of simple transformations:
We write out the value
, we get:
QED.
9. Gray code
Definition
Gray code is called a system of numbering negative numbers when the codes of two adjacent
numbers differ in exactly one bit.
For example, for the numbers of length 3 bits, we have a sequence of Gray codes:
,
,
,
,
,
.Eg
.
We shall go on to junior high order bits (albeit the least significant bit is numbered 1, and the oldest
- ). Obtain the following relations between the bits of bits and number :
Applications
Gray codes have several applications in different areas, sometimes quite unexpected:
[Difficulty: Medium]
The numbers can be used from one system or another value, commonly used decimal system and its
power (ten thousand billion), or binary system.
Operations on numbers in the form of a long arithmetic produced by a "school" of algorithms for
addition, subtraction, multiplication, long division. However, they are also useful algorithms for fast
multiplication: Fast Fourier transform and the Karatsuba algorithm.
Described here only work with non-negative long numbers. To support negative numbers must enter
and maintain additional flag "negativity" numbers, or else work in complementary codes.
Data Structure
Keep long numbers will be in the form of a vector of numbers
number.
Conclusion
The most simple - it's the conclusion of a long number.
First, we simply display the last element of the vector (or , if the vector is empty), and then derive all
the remaining elements of the vector, adding zeros to their characters:
printf ("%d", a.empty() ? 0 : a.back());
(Here, a little subtle point: not to forget to write down the cast
, because otherwise the
number
will be unsigned, and if
, it will happen in the subtraction overflow)
Reading
Reads a line in
the array
Addition
Adds to the number of the number and stores the result in :
int carry = 0;
for (size_t i=0; i<max(a.size(),b.size()) || carry; ++i) {
if (i == a.size())
a.push_back (0);
a[i] += carry + (i < b.size() ? b[i] : 0);
carry = a[i] >= base;
if (carry) a[i] -= base;
}
Subtraction
Takes the number of the number of (
int carry = 0;
for (size_t i=0; i<b.size() || carry; ++i) {
a[i] -= carry + (i < b.size() ? b[i] : 0);
carry = a[i] < 0;
if (carry) a[i] += base;
}
while (a.size() > 1 && a.back() == 0)
a.pop_back();
Here we after subtraction remove leading zeros in order to maintain a predicate that they do not exist.
int carry = 0;
for (size_t i=0; i<a.size() || carry; ++i) {
if (i == a.size())
a.push_back (0);
long long cur = carry + a[i] * 1ll * b;
a[i] = int (cur % base);
carry = int (cur / base);
}
while (a.size() > 1 && a.back() == 0)
a.pop_back();
Here we after dividing remove leading zeros in order to maintain a predicate that they do not exist.
(Note: The method further optimization . If performance is critical, you can try to replace the two
division one: to count only the integer portion of a division (in the code is a variable
), and then
count on it the remainder of the division (with the help of one multiplication) . Typically, this technique
allows faster code, but not very much.)
int carry = 0;
for (int i=(int)a.size()-1; i>=0; --i) {
long long cur = a[i] + carry * 1ll * base;
a[i] = int (cur / b);
carry = int (cur % b);
}
while (a.size() > 1 && a.back() == 0)
a.pop_back();
Thus, this method saves memory compared to the "classical" long arithmetic (although in some cases
not as radically as the factorization method). In addition, in a modular fashion, you can very quickly
make addition, subtraction and multiplication, - all for adding identical time asymptotically proportional
to the number of modules in the system.
However, all this is very time consuming given the price of the translation of this modular form in the
usual form, which, in addition to considerable time-consuming, require also the implementation of
"classical" long arithmetic multiplication.
In addition, to make the division of numbers in this representation in simple modules is not possible.
where and - are relatively prime (note: if they are not relatively prime, then the algorithm
described below is incorrect, though, presumably, it can be modified so that it is still working).
Here we describe an algorithm, known as the "Baby-Step-giant-Step algorithm" , proposed
by Shanks (Shanks) in 1971, working at the time for
. Often, this algorithm is simply
Algorithm
So, we have the equation:
where and
), as
In order to solve the original equation, you need to find the appropriate values and to the values of
the left and right parts of the match. In other words, it is necessary to solve the equation:
This problem is solved using the meet-in-the-middle as follows. The first phase of the algorithm:
calculate the value of the function for all values of the argument , and we can sort the values. The
second phase of the algorithm: Let's take the value of the second variable , compute the second
function , and look for the value of the predicted values of the first function using a binary search.
Asymptotics
First, we estimate the computation time of each of the functions
and
. And she and the
other contains the construction of the power that can be performed using the algorithm binary
exponentiation . Then both of these functions, we can compute in time
.
The algorithm itself in the first phase comprises computing functions
and the further sorting of values that gives us the asymptotic behavior:
Note. We could exchange roles and (ie the first phase to calculate the values of the function ,
and the second - ), but it is easy to understand that the result will not change, and the asymptotic
behavior that we can not improve.
Implementation
The simplest implementation
The function
exponentiation .
, see. binary
The function
produces a proper solution to the problem. This function returns a response (the
number in the interval
), or more precisely, one of the answers.Function will return
if there is
no solution.
int powmod (int a, int b, int m) {
int res = 1;
while (b > 0)
if (b & 1) {
res = (res * a) % m;
--b;
}
else {
a = (a * a) % m;
b >>= 1;
}
return res % m;
}
int solve (int a, int b, int m) {
int n = (int) sqrt (m + .0) + 1;
map<int,int> vals;
for (int i=n; i>=1; --i)
vals[ powmod (a, i * n, m) ] = i;
for (int i=0; i<=n; ++i) {
int cur = (powmod (a, i, m) * b) % m;
if (vals.count(cur)) {
int ans = vals[cur] * n - i;
if (ans < m)
return ans;
}
}
return -1;
}
Here we are for the convenience of the implementation of the first phase of the algorithm used the
data structure "map" (red-black tree) that for each value of the function
stores the argument in
which this value is reached. Here, if the same value is achieved repeatedly recorded smallest of all
the arguments. This is done to ensure that subsequently, in the second phase of the algorithm found
in the response interval
.
Given that the argument of
the first phase we pawing away from one and up , and the argument
of the function
in the second phase moves from zero to , in the end, we cover the entire set of
possible answers, because segment
contains a gap
. In this case, the negative response
could not get, and the responses of greater than or equal , we can not ignore - should still be in the
corresponding period of the answers
.
This function can be changed in the event that if you want to find all the solutions of the discrete
logarithm. To do this, replace the "map" on any other data structure that allows for a single argument
to store multiple values (for example, "multimap"), and to amend the code of the second phase.
An improved
When optimizing for speed , you can proceed as follows.
Firstly, immediately catches the eye uselessness binary exponentiation in the second phase of the
algorithm. Instead, you can just make it multiply the variable and every time .
Secondly, in the same way you can get rid of the binary exponentiation, and in the first phase: in fact,
once is enough to calculate the value
, and then simply multiply the at her.
Thus, the logarithm of the asymptotic behavior will remain, but it will only log associated with the data
structure
(ie, in terms of the algorithm, with sorting and binary search values) - ie it will be
the logarithm of
that in practice gives a noticeable boost.
int solve (int a, int b, int m) {
int n = (int) sqrt (m + .0) + 1;
int an = 1;
for (int i=0; i<n; ++i)
an = (an * a) % m;
map<int,int> vals;
for (int i=1, cur=an; i<=n; ++i) {
if (!vals.count(cur))
vals[cur] = i;
cur = (cur * an) % m;
}
for (int i=0, cur=b; i<=n; ++i) {
if (vals.count(cur)) {
}
cur = (cur * a) % m;
return -1;
}
Finally, if the unit is small enough, it can and does get rid of the logarithm in the asymptotic
behavior - instead of just having got
a regular array.
You can also recall the hash table: on average, they work well for
asymptotic behavior
.
where
Below we consider some classical problems on these equations: finding any solution, obtaining all
solutions, finding the number of solutions and the solutions themselves in a certain interval, to find a
solution with the least amount of unknowns.
Degenerate case
A degenerate case we immediately excluded from consideration when
. In this case, of
course, the equation has infinite number of random or solutions, or it has no solution at all (depending
on whether
or not).
We have described the decision in the case where the number and non-negative. If one of them or
both are negative, then we can proceed as follows: take their modulus and apply them Euclid's
algorithm, as described above, and then found to change the sign and the present symbol
numbers and , respectively.
Implementation (recall here, we believe that the input data
number
from
Obviously, this process can be repeated any number, ie all numbers of the form:
, we
, ie
(since the
We give implementation (it is difficult to obtain because it requires carefully consider the cases of
positive and negative coefficients and )
void shift_solution (int & x, int & y, int a, int b, int cnt) {
x += cnt * b;
y -= cnt * a;
}
int find_all_solutions (int a, int b, int c, int minx, int maxx, int miny,
int maxy) {
int x, y, g;
if (! find_any_solution (a, b, c, x, y, g))
return 0;
a /= g; b /= g;
int sign_a = a>0 ? +1 : -1;
int sign_b = b>0 ? +1 : -1;
shift_solution (x, y, a, b, (minx - x) / b);
if (x < minx)
shift_solution (x, y, a, b, sign_b);
if (x > maxx)
return 0;
int lx1 = x;
shift_solution (x, y, a, b, (maxx - x) / b);
if (x > maxx)
shift_solution (x, y, a, b, -sign_b);
int rx1 = x;
shift_solution (x, y, a, b, - (miny - y) / a);
if (y < miny)
shift_solution (x, y, a, b, -sign_a);
if (y > maxy)
return 0;
int lx2 = x;
shift_solution (x, y, a, b, - (maxy - y) / a);
if (y > maxy)
shift_solution (x, y, a, b, sign_a);
int rx2 = x;
if (lx2 > rx2)
swap (lx2, rx2);
int lx = max (lx1, lx2);
int rx = min (rx1, rx2);
return (rx - lx) / abs(b) + 1;
}
Also it is easy to add to this realization the withdrawal of all the solutions found: it is enough to
enumerate in a segment
with a step by finding for each of them corresponding directly
from Eq
.
changes as follows:
Ie if
it is necessary to choose the smallest possible value , if
the largest possible value .
If
it is necessary to choose
we can not improve the solution - all solutions will have the same amount.
[Difficulty: Medium]
where
Now consider the case and are not relatively prime . Then, obviously, the decision will not always
exist (for example ).
Suppose
one).
, that is, their greatest common divisor (which in this case is greater than
Then, if not divisible by then no solution exists. In fact, if any left side of the equation,
i.e.
, is always divisible by , while the right part it is not divided, which implies that
there are no solutions.
If it is divisible by , then dividing both sides by it (ie, dividing , and
equation:
on ), we arrive at a new
where and already be relatively prime, and this equation we have learned to solve. We denote its
solution through .
Clearly, this will also be a solution of the original equation. If, however
, it is not the
only solution. It can be shown that the original equation will have exactly the decisions and they will
look like:
To summarize, we can say that the number of solutions of linear modular equations is
either
, or zero.
, where
Then the correspondence (between numbers and tuples) will be one to one . And, moreover, the
operations performed on the number , can be equivalently performed on the corresponding element
of the tuple - by the independent performance of operations on each component.
That is, if
then we have:
In its original formulation of this theorem was proved by the Chinese mathematician Sun Tzu around
100 AD Namely, he showed in the particular case of the equivalence of solutions of the system of
modular equations and solutions of one of the modular equation (see. Corollary 2 below).
Corollary 1
Modular system of equations:
, the numbers
- an arbitrary
Corollary 2
The consequence is a connection between the system of modular equations and a corresponding
modular equation:
The equation:
, the number
Algorithm Garner
Of the Chinese remainder theorem, it follows that it is possible to replace operations on the number of
operations on tuples. Recall, each number is assigned a tuple
, where:
It can be widely used in practice (in addition to the direct application for the restoration of its residues
on the different modules), as we thus can replace surgery in the long arithmetic operations with an
array of "short" numbers. Say, an array of
elements of "enough" to number around
characters (if selected as the first -s
simple); and if selected as a simple -s about a billion,
then enough already by with about
signs. But, of course, then you need to learn how
to restore by this tuple. From Corollary 1 shows that such a recovery is possible and, moreover, the
only (provided
). Garner algorithm is an algorithm that allows to perform
this restoration, with effectively.
We seek a solution in the form of:
ie mixed value with digit weights
We denote by
(
,
) the number of which is the inverse
modulo (finding the inverse elements in the ring mod described here :
and dividing by
of the time
It should be noted that in practice almost always need to calculate the answer using the Long
arithmetic , but the coefficients themselves are still calculated on the built-in types, but because the
whole algorithm Garner is very effective.
void init() {
for (int x=1000*1000*1000, i=0; i<SZ; ++x)
if (BigInteger.valueOf(x).isProbablePrime(100))
pr[i++] = x;
for (int i=0; i<SZ; ++i)
for (int j=i+1; j<SZ; ++j)
r[i][j] = BigInteger.valueOf( pr[i] ).modInverse(
BigInteger.valueOf( pr[j] )
).intValue();
}
class Number {
int a[] = new int[SZ];
public Number() {
}
public Number (int n) {
for (int i=0; i<SZ; ++i)
a[i] = n % pr[i];
}
public Number (BigInteger n) {
for (int i=0; i<SZ; ++i)
a[i] = n.mod( BigInteger.valueOf( pr[i] )
).intValue();
}
public Number add (Number n) {
Number result = new Number();
for (int i=0; i<SZ; ++i)
result.a[i] = (a[i] + n.a[i]) % pr[i];
return result;
}
public Number subtract (Number n) {
Number result = new Number();
for (int i=0; i<SZ; ++i)
result.a[i] = (a[i] - n.a[i] + pr[i]) % pr[i];
return result;
}
public Number multiply (Number n) {
);
return result;
}
public BigInteger bigIntegerValue (boolean can_be_negative) {
BigInteger result = BigInteger.ZERO,
mult = BigInteger.ONE;
int x[] = new int[SZ];
for (int i=0; i<SZ; ++i) {
x[i] = a[i];
for (int j=0; j<i; ++j) {
long cur = (x[i] - x[j]) * 1l * r[j][i];
x[i] = (int)( (cur % pr[i] + pr[i]) % pr[i]
);
}
result = result.add( mult.multiply(
BigInteger.valueOf( x[i] ) ) );
mult = mult.multiply( BigInteger.valueOf( pr[i] )
);
}
if (can_be_negative)
if (result.compareTo( mult.shiftRight(1) ) >= 0)
result = result.subtract( mult );
return result;
}
}
Support for the negative numbers deserves mention (flag
function
). Modular scheme itself does not imply differences between positive
and negative numbers. However, it can be seen that if a particular problem the answer modulo does
not exceed half of the product of all primes, the positive numbers will be different from the negative
that the positive numbers turn out less than this mid-, and negative - more. Therefore, we are after
classical algorithm Garner compare the result with the middle, and if it is, then we derive a minus, and
invert the result (ie, subtract it from the product of all primes, and print it already).
, ie find
Note that each member of the th of this work is divided into , ie allows one to answer; the number
of such members of the same
.
Further, we note that each th term of this series is divided into , ie gives one more to the answer
(given that in the first degree has already been considered before); the number of such members of
the same
th term of the series gives one to answer, and the number of members
This amount, of course, is not infinite, because only the first about
zero. Consequently, the asymptotic behavior of the algorithm is
Implementation:
int fact_pow (int n, int k) {
int res = 0;
while (n) {
n /= k;
res += n;
}
return res;
}
Ternary balanced system of value - a non-standard positional number system. The base system is
equal , but it differs from the usual ternary system that figures are
. As used
for single
digit is very uncomfortable, it usually takes some special notation. Conditions are denoted by minus
one letter .
For example, the number in the ternary system is balanced as written
, and the number
as
. Ternary balanced system value allows you to record negative numbers without writing a
single sign "minus". Balanced ternary system allows fractional numbers (for example,
is written
as
).
Translation algorithm
Learn how to translate the numbers in a balanced ternary system.
To do this, we must first convert the number in the ternary system.
It is clear that now we have to get rid of the numbers , for which we note that
, ie we can
replace the two in the current discharge on
, while increasing the next (ie, to the left of it in a
natural writing) on the discharge . If we move from right to left on the record and perform the above
operation (in this case in some discharges can overflow more , in this case, of course, "reset" extra
triple in the MSB), then arrive at a balanced ternary recording. As is easily seen, the same rule holds
true for fractional numbers.
More gracefully above procedure can be described as follows. We take the number in the ternary
value is added to it an infinite number
, then each bit of the result subtract one
(already without any hyphens).
Knowing now the translation algorithm from the usual ternary system in a balanced, we can easily
implement the operations of addition, subtraction, and division - just reducing them to the
corresponding operations on ternary unbalanced numbers.
Algorithm
Let us write down this "modified" factorial explicitly:
When such a record shows that the "modified" factorial divided into several blocks of length (the last
block may have shorter), which are all identical, except for the last element:
And again we come to the "modified" factorial, but has a smaller dimension (as much as it was full of
blocks, and they were
). Thus, the calculation of "modified" factorial
we have reduced
due
to the computation operations already
. Expanding this recurrence relation, we
find that the depth of recursion is
, totalasymptotic behavior of the algorithm is
obtained
.
Implementation
It is clear that the implementation is not necessary to use recursion explicitly: as tail recursion, it is
easy to deploy in the cycle.
int factmod (int n, int p) {
int res = 1;
while (n > 1) {
res = (res * ((n/p) % 2 ? p-1 : 1)) % p;
for (int i=2; i<=n%p; ++i)
res = (res * i) % p;
n /= p;
}
return res % p;
}
This implementation works for
... s m ...
We prove that the inner loop will execute a total
iterations.
Proof: 1 way . Consider th bit. For him, generally speaking, there are exactly three ways: it is not
included in the mask (and therefore in the subpattern ); it is included in , but is not included ; it
enters in . Total bits , so all the different combinations will
, as required.
Proof: 2 way . Note that if the mask
of the mask
Calculate this amount. To do this, we note that it is nothing like the binomial theorem expansion in the
expression
, ie
, as required.
The existence of
Primitive root modulo
in cases
,
This theorem (which was fully proved by Gauss in 1801) is given here without proof.
which
(ie - index (multiplicative order)), power
. Moreover, the converse
is also true, and this fact will be used by us in the following algorithm for finding a primitive root.
Furthermore, if the modulus is at least one primitive root, the total of
with elements has
generators).
(ie - index
a divisor
is performed
. Thus, it suffices to
.This is a much faster
. However, if
. Then,
we
numbers
, and for each consider all the values
numbers were different from , this is the desired primitive root.
Implementation
Function powmod () performs a binary exponentiation modulo a function generator (int p) - is a
primitive root modulo a prime (factorization of
the simplest algorithm is implemented
for
).
To adapt this function to arbitrary , just add the calculation of Euler's function in a variable
well as weed out
non-prime to .
int powmod (int a, int b, int p) {
int res = 1;
while (b)
if (b & 1)
res = int (res * 1ll * a % p),
else
--b;
, as
b >>= 1;
return res;
where
Here is an unknown quantity , so we came to the discrete logarithm problem in a pure form. This problem can
be solved by an algorithm baby-step-giant-step Shanks for
this equation (or find that this equation has no solutions).
of
For this recall is the fact that a primitive root always has order
(see. article about primitive root ),
ie the least degree , giving the unit is
. Therefore, the addition of the term with the
exponent
does not change anything:
numbers
), we obtain:
This is the final convenient formula, which gives a general view of all the solutions of the discrete root.
Implementation
We give a full implementation, including finding a primitive root, and finding the discrete logarithm and
the withdrawal of all decisions.
int gcd (int a, int b) {
return a ? gcd (b%a, a) : b;
}
int powmod (int a, int b, int p) {
int res = 1;
while (b)
if (b & 1)
res = int (res * 1ll * a % p), --b;
else
a = int (a * 1ll * a % p), b >>= 1;
return res;
}
int main() {
int n, k, a;
cin >> n >> k >> a;
if (a == 0) {
puts ("1\n0");
return 0;
}
int g = generator (n);
int sq = (int) sqrt (n + .0) + 1;
vector < pair<int,int> > dec (sq);
for (int i=1; i<=sq; ++i)
dec[i-1] = make_pair (powmod (g, int (i * sq * 1ll * k % (n 1)), n), i);
sort (dec.begin(), dec.end());
int any_ans = -1;
for (int i=0; i<sq; ++i) {
int my = int (powmod (g, int (i * 1ll * k % (n - 1)), n) * 1ll
* a % n);
vector < pair<int,int> >::iterator it =
lower_bound (dec.begin(), dec.end(), make_pair (my,
0));
if (it != dec.end() && it->first == my) {
any_ans = it->second * sq - i;
break;
}
}
if (any_ans == -1) {
puts ("0");
return 0;
The classic way to solve this problem - the sieve of Eratosthenes . This algorithm is very simple, but
it works for a while
.
Although at the moment we know a lot of algorithms working in sublinear time (ie
), the algorithm
described below is interesting for its simplicity - it is practically difficult to classical sieve of
Eratosthenes.
In addition, the algorithm presented here as a "side effect" is actually computes a factorization of all
the numbers in the interval
, which can be useful in many practical applications.
The drawback of the algorithm is driven by the fact that it uses more memory than the classical sieve
of Eratosthenes: start requires an array of numbers, while the classical sieve of Eratosthenes only
enough bits of memory (which is obtained in
half).
Thus, the described algorithm should be applied only up to the order of numbers
, no more.
Authorship algorithm apparently belongs Grice and Misra (Gries, Misra, 1978 - see. Bibliography at
the end). (And, in fact, to call this algorithm "Sieve of Eratosthenes" correctly too the difference
between these two algorithms.)
- This means that the number - easy because for it had not found other subgroups.
Therefore, it is necessary to assign
- This means that the current number - a composite, and it is a minimal prime divisor
In both cases, then begins the process of alignment of values in the array
: we will take the
number, multiples , and update their value
. However, our goal - to learn how to do this so that
in the end each of the value
would be set more than once.
Argues that it is possible to do so. Consider the number of the form:
Why such an algorithm is correct, and why it works in linear time - see. Below, but for now we present
its implementation.
Implementation
Sieve performed until the number of the constant
Proof of correctness
We prove the correctness of the algorithm, ie he correctly puts all the values
, and each of them
will be set only once. This will imply that the algorithm works in linear time - as all the other steps of
the algorithm is obviously working for
.
For this, note that any number of unique representation of this form:
where
less
- (as before) a minimal prime divisor of the number , and the number has no divisors
, ie .:
Now compare this to what makes our algorithm - it is actually for everyone through all simple, for
which it can multiply, ie Just prior
inclusive, to obtain the number of the above representation.
Consequently, the algorithm does take place for each composite number exactly once, putting it the
correct value
.
This means the correctness of the algorithm and the fact that it runs in linear time.
Literature
David Gries, Jayadev Misra. A Linear Sieve Algorithm for Finding Prime Numbers [1978]
gives the result "simple"). At the same time, Carl Pomerance in 1984 presented a heuristic proof that
there are infinitely many BPSW-pseudosimple numbers.
Complexity of the algorithm BPSW is O (log 3 (N)) bit operations. If we compare the algorithm BPSW
with other tests, such as the Miller-Rabin test, the algorithm BPSW is usually 3-7 times slower.
Algorithm is often used in practice. Apparently, many commercial mathematical packages, wholly or
partly rely on an algorithm to check BPSW Primality.
Brief description of
The algorithm has several different implementations, differing only in details. In this case, the
algorithm is:
1 Run Miller-Rabin test to the base 2.
2 Run a strong Lucas-Selfridge test using Lucas sequence with parameters Selfridge.
3 Return the "simple" only when both tests returned "simple".
+0. In addition, at the beginning of the algorithm can add a check for trivial divisors, say, 1000 This
will increase the speed of operation on a composite number, however, has slowed somewhat in the
simple algorithm.
Thus, the algorithm BPSW based on the following:
1 (true) test and the Miller-Rabin test Lucas-Selfridge and if wrong, it is only one way: some
components of these algorithms are recognized as simple. Conversely, these algorithms do not make
mistakes ever.
2 (assumption) Miller-Rabin test and the test of Lucas-Selfridge and if wrong, that are never wrong on
one number at a time.
In fact, the second assumption seems to be as incorrect - heuristic proof-refutation Pomerance
below. However, in practice, no one pseudosimple still have not found, so we can assume conditional
second assumption correct.
Miller-Rabin test
I will not focus on the Miller-Rabin test, as it is described in many sources, including in Russian (for
example., See. [5] ).
My only comment is that its speed is O (log 3 (N)) bit operations and bring a ready implementation of
this algorithm:
template <class T, class T2>
bool miller_rabin (T n, T2 b)
{
// First check the trivial cases
if (n == 2)
return true;
if (n <2 || even (n))
return false;
// Check that n and b are relatively prime (otherwise it will cause
an error)
// If they are not relatively prime, then n is not a simple, or it
is necessary to increase the b
if (b <2)
b = 2;
for (T g; (g = gcd (n, b))! = 1; ++ b)
if (n> g)
return false;
// Decompose n-1 = q * 2 ^ p
T n_1 = n;
--n_1;
T p, q;
transform_num (n_1, p, q);
// Calculate b ^ q mod n, if it is equal to 1 or n-1, n is prime (or
pseudosimple)
T rem = powmod (T (b), q, n);
if (rem == 1 || rem == n_1)
return true;
// Now compute b ^ 2q, b ^ 4q, ..., b ^ ((n-1) / 2)
// If any of them is equal to n-1, n is prime (or pseudosimple)
for (T i = 1; i <p; i ++)
{
mulmod (rem, rem, n);
if (rem == n_1)
return true;
return false;
}
Algorithm Selfridge
Among the sequences 5, -7, 9, -11, 13, ... to find the first number D, for which J (D, N) = -1 and gcd
(D, N) = 1, where J (x, y) - Jacobi symbol.
Selfridge parameters are P = 1 and Q = (1 - D) / 4.
It should be noted that the parameter does not exist for Selfridge properties that are precise
squares. Indeed, if the number is a perfect square, the bust D comes to sqrt (N), where it appears
that gcd (D, N)> 1, ie, found that the number N is composite.
In addition, Selfridge parameters will be calculated incorrectly for even numbers and units; however,
verification of these cases will not be difficult.
Thus, before the start of the algorithm should check that the number N is odd, greater than 2, and
is not a perfect square, otherwise (under penalty of at least one condition), you should immediately
exit the algorithm with the result of a "composite".
Finally, we note that if D for some number N is too large, then the algorithm from a computational
point of view, would be inapplicable. Although in practice this has not been noticed (are sufficient 4byte number), though the probability of this event should not be excluded. However, for example, in
the interval [1; 10 6 ] max (D) = 47, and in the interval [10 19 ; 10 19 10 6 ] max (D) = 67. Furthermore,
Bailey and Wagstaff 1980 analytically proved that observation (see. Ribenboim, 1995/96, p. 142).
= 0
1 = 1,
U K = PU
V 0 = 2
V 1 = P
V K = PV
0
K-1
- QU
-K 2
K-1
- QV
K-2
= 0 (mod N)
N + 1
= 0 (mod N)
The converse is not true in general. Nevertheless, pseudosimple numbers when the algorithm is not
very much on what, in fact, is based algorithm Lucas.
Thus, the algorithm is to calculate the Lucas U M and compare it with zero .
Next, you need to find some way to speed up computation U K , otherwise, of course, no practical
sense in this algorithm would not be.
We have:
U
V
K
K
= (A K - b K ) / (A - b),
A = K b + K ,
2K
2K
U =
V =
K
K
V
2
K (mod N)
- 2 Q K (mod N)
= U
2E
4E
... V
T-2
T-1
= 0 (mod N) ,
First, consider the following formulas for the addition of members of Lucas sequences:
U
V
+ J I
+ J I
= (U
= (V
I
I
V
V
J
J
+ U J V I ) / 2 (mod N)
+ DU I U J ) / 2 (mod N)
- 4 * Q? 0 .
T2 dd;
for (T2 d_abs = 5, d_sign = 1;; d_sign = -d_sign, ++++ d_abs)
{
dd = d_abs * d_sign;
T g = gcd (n, d_abs);
if (1 <g && g <n)
// Found divider - d_abs
return false;
if (jacobi (T (dd), n) == -1)
break;
}
// Parameters Selfridge
T2
p = 1,
q = (p * p - dd) / 4;
// Expand the n + 1 = d * 2 ^ s
T n_1 = n;
++ N_1;
T s, d;
transform_num (n_1, s, d);
// Algorithm Lucas
T
u = 1,
v = p,
u2m = 1,
v2m = p,
qm = q,
qm2 = q * 2,
qkd = q;
for (unsigned bit = 1, bits = bits_in_number (d); bit <bits; bit ++)
{
mulmod (u2m, v2m, n);
mulmod (v2m, v2m, n);
while (v2m <qm2)
v2m + = n;
v2m - = qm2;
mulmod (qm, qm, n);
qm2 = qm;
redouble (qm2);
if (test_bit (d, bit))
{
T t1, t2;
t1 = u2m;
mulmod (t1, v, n);
t2 = v2m;
mulmod (t2, u, n);
T t3, t4;
t3 = v2m;
mulmod (t3, v, n);
t4 = u2m;
mulmod (t4, u, n);
Code BPSW
It now remains to simply combine the results of all three tests: checking for small trivial divisors,
Miller-Rabin test, test strong Lucas-Selfridge.
Quick implementation
Code length can be significantly reduced at the expense of flexibility, giving up templates and various
support functions.
const int trivial_limit = 50;
int p [1000];
int gcd (int a, int b) {
return a? gcd (b% a, a): b;
}
int powmod (int a, int b, int m) {
int res = 1;
while (b)
if (b & 1)
res = (res * 1ll * a)% m, --b;
else
a = (a * 1ll * a)% m, b >> = 1;
return res;
}
bool miller_rabin (int n) {
int b = 2;
for (int g; (g = gcd (n, b))! = 1; ++ b)
if (n> g)
return false;
int p = 0, q = n-1;
if (pr)
p [j ++] = i;
], for which:
), n = -1 (mod Q
T 2 (1 - 3 / K)
/ e
2T
> e
T 2 (1 - 4 / k)
for large T.
But every such n - is a counterexample to the test BPSW . Indeed, n is the number of Carmichael
(ie, the number on which the Miller-Rabin test is wrong for any reason), so it will automatically
pseudosimple base 2 Since n = 3 (mod 8) and each p | n = 3 (mod 8), it is obvious that n is also a
strong base 2 pseudosimple Since J (5, n) = -1, then every prime p | n satisfies J (5, p) = -1, and
since the p + 1 | n + 1 for any prime p | n, it follows that n - pseudosimple Lucas Lucas for any test
with discriminant 5.
Thus, we have shown that for any fixed k and all large T, there will at least e T 2 (1 - 4 /
k)
counterexamples to test BPSW of numbers less than e T 2 . Now, if we put x = e T 2 , x is at least 1 - 4 /
k
counterexamples smaller x. Since k - a random number, then our evidence indicates that the
number of counterexamples, smaller x, is a number greater than x 1-a for any a> 0 .
The average operating time on the segment number, depending on the limit of
the trivial enumeration
This refers to the parameter passed to the function prime_div_trivial (), which in the above code is 29.
Download a test program (source code and exe-file). [83 KB]
If you run a test on all the odd numbers in the interval, the results turn out to be:
the beginning
of the segment
End
segments
limit>
iterate>
10 2
10 5
8.1
4.5
10 6
10 6 10 5
12.8
6.8
10 9
10 9 10 5
28.4
12.6
10 12
10 12 10 5
41.5
16.5
10 15
10 15 10 5
66.7
24.4
If the test is run only on the primes in the interval, the rate of work is as follows:
the beginning
of the segment
End
segments
limit>
iterate>
10 2
10 5
42.9
40.8
10 6
10 6 10 5
75.0
76.4
10 9
10 9 10 5
186.5
188.5
10 12
10 12 10 5
288.3
288.3
10 15
10 15 10 5
485.6
489.1
End
segments
while working
on the odd numbers
time work
on prime numbers
10 5
1.2
4.2
10 6
10 6 10 5
13.8
88.8
10 7
10 7 10 5
16.8
115.5
10 8
10 8 10 5
21.2
164.8
10 9
10 9 10 5
24.0
201.0
10 10
10 10 10 5
25.2
225.5
10 11
10 11 10 5
28.4
266.5
10 12
10 12 10 5
30.4
302.2
10 13
10 13 10 5
33.0
352.2
10 14
10 14 10 5
37.5
424.3
10 15
10 15 10 5
42.3
499.8
10 16
10 15 10 5
46.5
553.6
10 17
10 15 10 5
48.9
621.1
Or, in the form of a graph, the approximate time of the test on one BPSW including:
That is, we have found that in practice, a small number (10 17 ), the algorithm runs in O (Log
N) . This is due to the fact that the embedded type int64 division operation is performed in O (1),
i.e. dividing complexity zavisisit not the number of bits in number.
If we apply the test to a long BPSW arithmetic, it is expected that it will work just for the O
(log 3 (N)). [TODO]
Literature
Usable me literature, is available online:
1. Robert Baillie; Samuel S. Wagstaff Lucas pseudoprimes Math. Comp. 35 (1980) 13911417 mpqs.free.fr/LucasPseudoprimes.pdf
2. Daniel J. Bernstein Distinguishing Prime numbers from Composite numbers: the State of the art
in 2004 Math. Comp. (2004) cr.yp.to/primetests/prime2004-20041223.pdf
3. Richard P. Brent Primality Testing and Integer factorisation The Role of Mathematics in Science
(1990) wwwmaths.anu.edu.au/~brent/pd/rpb120.pdf
11. Paulo Ribenboim The Book of Prime Number Records Springer-Verlag (1989) [no link]
13. Hans Riesel Prime numbers and computer Methods for Factorization Boston: Birkhauser (1994)
if (aa == 0)
continue;
// Check not found the answer
T g = gcd (aa-1, n);
if (1 <g && g <n)
return g;
}
}
// If nothing found
return 1;
}
is_prime = false;
break;
}
}
if (is_prime)
primes.push_back (prime);
}
T g = 1;
for (size_t i = 0; i <primes.size () && g == 1; i ++)
{
T cur = primes [i];
while (cur <= n)
cur * = primes [i];
cur / = primes [i];
b = powmod (b, cur, n);
g = gcd (abs (b-1), n);
if (g == n)
g = 1;
}
return g;
}
Method Farm
This wide method, but it can be very slow if the number is small divisors.
Therefore, it should run only after all other methods.
template <class T, class T2>
T ferma (const T & n, T2 unused)
{
T2
x = sq_root (n),
y = 0,
r = x * x - y * y - n;
for (;;)
if (r == 0)
return x! = y? xy: x + y;
else
if (r> 0)
{
r - = y + y + 1;
++ Y;
}
else
{
r + = x + x + 1;
++ X;
}
}
Trivial division
This basic method is useful to immediately handle numbers with very small divisors.
template <class T, class T2>
T2 prime_div_trivial (const T & n, T2 m)
{
// First check the trivial cases
if (n == 2 || n == 3)
return 1;
if (n <2)
return 0;
if (even (n))
return 2;
// Generate a simple 3 to m
T2 pi;
const vector <T2> & primes = get_primes (m, pi);
// Divisible by all prime
for (std :: vector <T2> :: const_iterator iter = primes.begin (),
end = primes.end ();
iter! = end; ++ Iter)
{
const T2 & div = * iter;
if (div * div> n)
break;
else
if (n% div == 0)
return div;
}
if (n <m * m)
return 1;
return 0;
}
algorithms
if (n <1000 * 1000)
{
T div = prime_div_trivial (n, 1000);
++ Result [div];
factorize (n / div, result, unused);
}
else
{
// Number of large, run it factorization
T div;
// First go fast algorithms Pollard
div = pollard_monte_carlo (n);
if (div == 1)
div = pollard_rho (n);
if (div == 1)
div = pollard_p_1 (n);
if (div == 1)
div = pollard_bent (n);
// Will run 100% algorithm Farm
if (div == 1)
div = ferma (n, unused);
// Recursively Point Multipliers
factorize (div, result, unused);
factorize (n / div, result, unused);
Appendix
Download [5k] source program that uses all of these methods and test factorization BPSW on
simplicity.
of degree th:
Of the theory of functions of a complex variable is known that the complex roots
exactly
of unity th exists
. In addition,
th roots of unity) is
Then the discrete Fourier transform (DFT) (discrete Fourier transform, DFT) of the
polynomial
(or, equivalently, the DFT of the vector of its coefficients
the values of the polynomial at the points
, ie, is a vector:
) are
Defined similarly and inverse discrete Fourier transform (InverseDFT). Inverse DFT to the vector of
a polynomial
- is the vector of coefficients of the polynomial
:
Thus, if the direct proceeds from the DFT coefficients of the polynomial to its values in the complex
roots of th roots of unity, the inverse DFT - on the contrary, from the values of the coefficients of the
polynomial recovers.
and
- two
Now, what happens when you multiply polynomials? Obviously, in each point of their values are
simply multiplied, that is,
where, again, right under the product of two DFT mean pairwise products of the elements of the
vectors. This work obviously requires to compute only
operations. Thus, if we learn to calculate
the DFT and inverse DFT of the time
, then the product of two polynomials (and,
consequently, the two long numbers) we can find for the same asymptotic behavior.
It should be noted that, firstly, the result should be two polynomials of degree one (simply adding the
coefficients of one of these zeros). Secondly, as a result of the product of two polynomials of
degree polynomial of degree obtained
, so that the result is correct, you need to double the
pre-degree of each polynomial (again, adding to their coefficients equal to zero).
of degree
, where
Divide it into two polynomials, one - with even and the other - with the odd coefficients:
The polynomials
and
have twice lower degree than the polynomial . If we can in linear time
from the calculated
and
calculate
, then we obtain the desired fast
Fourier transform algorithm (since it is a standard chart of "divide and conquer", and it is known
asymptotic estimate
).
So, suppose we have calculated the vector
. Let us find the expression for
and
.
Firstly, recalling (1), we immediately obtain the values for the first half of the coefficients:
For the second half of the coefficients after transformation also get a simple formula:
, and
.)
:
Inverse FFT
So, let a vector
- the values of a polynomial of degree at points
. Need to recover the coefficients
of the polynomial. This well-known problem is
called interpolation , for this task, there are some common algorithms for the solution, but in this
case will be obtained by a very simple algorithm (a simple fact that it is virtually identical to the FFT).
DFT, we can write, according to his definition, in matrix form:
The vector
can be found by multiplying the vector
by the
inverse matrix to the matrix, which stands on the left (which, incidentally, is called a Vandermonde
matrix):
Thus, we obtain:
we observe that these two tasks are not real, so that the coefficients
algorithm "divide and rule" as a direct FFT, but instead
every element of the result should be divided into .
, and
Thus, the calculation of the inverse DFT is not very different from the direct computation of the DFT,
and it can also be performed during the time
.
Implementation
Consider a simple recursive implementation of the FFT and IFFT, implement them in a single
function, as the difference between direct and inverse FFT minimal. For storing complex numbers
using the standard in C ++ STL type complex (defined in the header file <complex>).
typedef complex<double> base;
void fft (vector<base> & a, bool invert) {
int n = (int) a.size();
if (n == 1) return;
vector<base> a0 (n/2), a1 (n/2);
for (int i=0, j=0; i<n; i+=2, ++j) {
a0[j] = a[i];
a1[j] = a[i+1];
}
fft (a0, invert);
fft (a1, invert);
double ang = 2*PI/n * (invert ? -1 : 1);
base w (1), wn (cos(ang), sin(ang));
for (int i=0; i<n/2; ++i) {
a[i] = a0[i] + w * a1[i];
a[i+n/2] = a0[i] - w * a1[i];
if (invert)
a[i] /= 2, a[i+n/2] /= 2;
w *= wn;
}
}
In the argument of the function is passed the input vector of coefficients in the same and it will
contain the result. Argument
shows direct or inverse DFT should be calculated. Inside the
function first checks if the vector length is equal to one, there is nothing else to do - he is the
answer. Otherwise, the vector is split into two vectors
and
for which recursively calculated
DFT. Then we calculate the value of
, and the plant variable containing the current degree
. Then calculated the elements of the result of the DFT on the above formulas.
If the flag is specified
, then
replaced by
, and each element of the result is
divided by 2 (given that these dividing by 2 will take place in each level of recursion, the result just
happens that all the elements on the share ).
element
invert the bit order, and reorder elements of the array according to the new indexes, we
obtain the desired order (it is called a bitwise inverse permutation (bit-reversal permutation)).
For example, in
Indeed, on the first level of recursion (surrounded by curly braces) conventional recursive algorithm is
a division of the vector into two parts:
and
. As we can see, in the
bitwise inverse permutation, this corresponds to a separation of the vector into two halves: the
first
element and the last
element. Then there is a recursive call on each half; let the
resulting DFT of each of them was returned in place of the elements themselves (ie, the first and
second halves of the vector , respectively):
Now we have to perform the union of two into one DFT for the vector. But the elements stood out so
well, and that the union can be performed directly in the array. Indeed, we take the elements
and is applicable to them transform butterflies, and the result is put in their place - and this place
and would thereby and which should have been received:
and
For example, suppose that - already counted the number equal to the inverse permutation of bits
. Then, during the transition to the next number
we have and the number of add one, but add it
to this "inverted" value. In a conventional binary value add one - so remove all units standing at the
end of the number (ie, a group of younger units), and put the unit in front of them. Accordingly, in the
"inverted" system we have to go the bit number, starting with the oldest, and while there are one,
delete them and move on to the next bit; when will meet the first zero bit, put it in the unit and stop.
Thus, we obtain a realization:
typedef complex<double> base;
void fft (vector<base> & a, bool invert) {
int n = (int) a.size();
for (int i=1, j=0; i<n; ++i) {
int bit = n >> 1;
for (; j>=bit; bit>>=1)
j -= bit;
j += bit;
if (i < j)
swap (a[i], a[j]);
}
for (int len=2; len<=n; len<<=1) {
double ang = 2*PI/len * (invert ? -1 : 1);
base wlen (cos(ang), sin(ang));
for (int i=0; i<n; i+=len) {
base w (1);
for (int j=0; j<len/2; ++j) {
base u = a[i+j], v = a[i+j+len/2] * w;
a[i+j] = u + v;
a[i+j+len/2] = u - v;
w *= wlen;
}
}
}
if (invert)
for (int i=0; i<n; ++i)
a[i] /= n;
}
Additional optimization
We give a list of other optimizations, which together can significantly speed up the preceeding
"improved" implementation:
Predposchitat reverse bits for all numbers in a global table. It is especially easy when the size
the same for all calls.
is
Refuse to use
( go to normal arrays ).
The effect of this depends upon the particular compiler, but typically it is present and approximately
10% -20%.
Accordingly, before this cycle we can predposchitat some array all the required power, and thus get
rid of unnecessary multiplications in the nested loop.
Tentative acceleration - 5-10%.
Get rid of references to arrays in the indices , instead use pointers to the current array elements,
promoting their right to 1 at each iteration.
At first glance, optimizing compilers should be able to cope with this, but in practice it turns out that
the replacement of references to arrays
and
pointers to accelerate the
program in popular compilers. Prize is 5-10%.
Again, this may seem surprising, but even in modern compilers benefit from such a rewriting can be
up to several tens of percent! This indirectly confirms a widespread assertion that compilers perform
worse with sample data types, optimizing work with them much worse than non-formulaic types.
Another useful optimization is the cut-off length : when the length of the working unit becomes small
(say, 4), to calculate the DFT for it "manually". If you paint these cases in the form of explicit formulas
for a length equal to
, the values of the sine-cosine take integer values, due to which you can get
a speed boost for another few tens of percent.
Here we present the realization of the described improvements (except the last two items which lead
to the proliferation of codes):
int rev[MAXN];
base wlen_pw[MAXN];
void fft (base a[], int n, bool invert) {
for (int i=0; i<n; ++i)
if (i < rev[i])
swap (a[i], a[rev[i]]);
for (int len=2; len<=n; len<<=1) {
if (invert)
for (int i=0; i<n; ++i)
a[i] /= n;
All other
roots th roots of unity in modulus
(as in the complex case).
For use in the fast Fourier transform algorithm we needed to primivny root existed for some , a
power of two, as well as all the lesser degrees. And if in the complex case, there was a primitive root
for anyone , in the case of modular arithmetic is generally not the case. However, note that
if
, ie Star power of two, the modulo
have:
Thus, if
- a primitive root of
th degree of unity, then
- a primitive root of
th roots of
unity. Therefore, all powers of two smaller , the primitive roots of the desired extent also exist and
can be calculated as the corresponding power
.
The final touch - for the inverse DFT, we used instead of
prime element inverse also always be found.
. But modulo a
Thus, all the required properties are observed in the case of modular arithmetic, provided that we
have chosen some rather large unit and found it to be a primitive root th roots of unity.
For example, you can take the following values: module
,
. If this module is
not enough to find another pair, you can use the fact that for the modules of the form
(but still
necessarily simple) there is always a primitive cube root of unity.
const
const
const
const
int
int
int
int
mod = 7340033;
root = 5;
root_1 = 4404020;
root_pw = 1<<20;
}
if (invert) {
int nrev = reverse (n, mod);
for (int i=0; i<n; ++i)
a[i] = int (a[i] * 1ll * nrev % mod);
}
}
Here, the function
mod ). Constants
modulo .
As practice shows, the implementation of integer DFT works even slower implementation of complex
numbers (due to the huge number of operations modulo), but it has advantages such as low memory
usage and the lack of rounding errors.
Some applications
In addition to direct application to multiply polynomials, or long numbers, we describe here are some
other applications of the discrete Fourier transform.
, we get:
Two strips
Given two strips defined as two Boolean (ie numeric with values 0 or 1) of the array
and . Want
to find all such positions in the first strip that if you apply, starting with this position, the second strip,
in any place will not work
right on both strips. This problem can be reformulated as follows: given
a map of the strip, as 0/1 - you can get up into the cell or not, and has some figure as a template (in
the form of an array, in which 0 - no cells, 1 - yes), requires find all the positions in the strip, which
can be attached figure.
This problem is in fact no different from the previous problem - the problem of the scalar
product. Indeed, the dot product of two arrays 0/1 - the number of elements in which both were
unity. Our task is to find all cyclic shifts of the second strip so that there was not a single element,
which would be in both strips were one. Ie we have to find all cyclic shifts of the second array, in
which the scalar product is zero.
Thus, this problem we decided for