You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/algebra/discrete-log.md
+8-3Lines changed: 8 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,7 +64,9 @@ $$O(\sqrt {m} \log m).$$
64
64
65
65
### The simplest implementation
66
66
67
-
In the following code, the function `powmod` calculates $a^b \pmod m$ and the function `solve` produces a proper solution to the problem. It returns $-1$ if there is no solution and returns one of the possible solutions otherwise.
67
+
In the following code, the function `powmod` calculates $a^b \pmod m$ and the function `solve` produces a proper solution to the problem.
68
+
It returns $-1$ if there is no solution and returns one of the possible solutions otherwise.
69
+
The resulting discrete logarithm can be big, but you can make it smaller using [Euler's theorem](./algebra/phi-function.html#toc-tgt-2).
68
70
69
71
```cpp
70
72
intpowmod(int a, int b, int m) {
@@ -85,7 +87,7 @@ int solve(int a, int b, int m) {
85
87
for (int p = n; p >= 1; --p)
86
88
vals[powmod(a, p * n, m)] = p;
87
89
for (int q = 0; q <= n; ++q) {
88
-
int cur = (powmod(a, q, m) * b) % m;
90
+
int cur = (powmod(a, q, m) * b) % m;
89
91
if (vals.count(cur)) {
90
92
int ans = vals[cur] * n - q;
91
93
return ans;
@@ -106,7 +108,10 @@ We also need to change the second step accordingly.
106
108
107
109
## Improved implementation
108
110
109
-
A possible improvement is to get rid of binary exponentiation. This can be done by keeping a variable that is multiplied by $a$ each time we increase $q$ and a variable that is multiplied by $a^n$ each time we increase $p$. With this change, the complexity of the algorithm is still the same, but now the $\log$ factor is only for `map`. Instead of a `map`, we can also use a hash table (`unordered_map` in C++) which has the average time complexity $O(1)$ for inserting and searching.
111
+
A possible improvement is to get rid of binary exponentiation.
112
+
This can be done by keeping a variable that is multiplied by $a$ each time we increase $q$ and a variable that is multiplied by $a^n$ each time we increase $p$.
113
+
With this change, the complexity of the algorithm is still the same, but now the $\log$ factor is only for the `map`.
114
+
Instead of a `map`, we can also use a hash table (`unordered_map` in C++) which has the average time complexity $O(1)$ for inserting and searching.
Copy file name to clipboardExpand all lines: src/algebra/discrete-root.md
+54-43Lines changed: 54 additions & 43 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
# Discrete Root
4
4
5
-
The problem of finding discrete root is defined as follows. Given a prime $n$ and two integers $a$ and $k$, find all $x$ for which:
5
+
The problem of finding a discrete root is defined as follows. Given a prime $n$ and two integers $a$ and $k$, find all $x$ for which:
6
6
7
7
$x^k \equiv a \pmod n$
8
8
@@ -14,7 +14,7 @@ Let's apply the concept of a [primitive root](./algebra/primitive-root.html) mod
14
14
15
15
We can easily discard the case where $a = 0$. In this case, obviously there is only one answer: $x = 0$.
16
16
17
-
Since we jnow that $n$ is a prime, any number between 1 and $n-1$ can be represented as a power of the primitive root, and we can represent the discrete root problem as follows:
17
+
Since we know that $n$ is a prime and any number between 1 and $n-1$ can be represented as a power of the primitive root, we can represent the discrete root problem as follows:
18
18
19
19
$(g^y)^k \equiv a \pmod n$
20
20
@@ -26,7 +26,7 @@ This, in turn, can be rewritten as
26
26
27
27
$(g^k)^y \equiv a \pmod n$
28
28
29
-
Now we have one unknown $y$, which is a discrete logarithm problem. The solution can be found using Shanks' baby-step-giant-step algorithm in $O(\sqrt {n} \log n)$ (or we can verify that there are no solutions).
29
+
Now we have one unknown $y$, which is a discrete logarithm problem. The solution can be found using Shanks' baby-stepgiant-step algorithm in $O(\sqrt {n} \log n)$ (or we can verify that there are no solutions).
30
30
31
31
Having found one solution $y_0$, one of solutions of discrete root problem will be $x_0 = g^{y_0} \pmod n$.
32
32
@@ -46,88 +46,99 @@ where $l$ is chosen such that the fraction must be an integer. For this to be tr
46
46
47
47
$x = g^{y_0 + i \frac {\phi (n)}{gcd(k, \phi (n))}} \pmod n \forall i \in Z$.
48
48
49
-
This is the final formula for all solutions of discrete root problem.
49
+
This is the final formula for all solutions of the discrete root problem.
50
50
51
51
## Implementation
52
52
53
-
Here is a full implementation, including routines for finding the primitive root, discrete log and finding and printing all solutions.
53
+
Here is a full implementation, including procedures for finding the primitive root, discrete log and finding and printing all solutions.
54
54
55
55
```cpp
56
-
intgcd(int a, int b) {
57
-
return a ? gcd (b%a, a) : b;
56
+
intgcd(int a, int b) {
57
+
return a ? gcd(b % a, a) : b;
58
58
}
59
59
60
-
int powmod(int a, int b, int p) {
60
+
int powmod(int a, int b, int p) {
61
61
int res = 1;
62
-
while (b)
63
-
if (b & 1)
64
-
res = int (res * 1ll * a % p), --b;
65
-
else
66
-
a = int (a * 1ll * a % p), b >>= 1;
62
+
while (b > 0) {
63
+
if (b & 1) {
64
+
res = res * a % p;
65
+
}
66
+
a = a * a % p;
67
+
b >>= 1;
68
+
}
67
69
return res;
68
70
}
69
71
70
-
int generator (int p) {
72
+
// Finds the primitive root modulo p
73
+
int generator(int p) {
71
74
vector<int> fact;
72
-
int phi = p-1, n = phi;
73
-
for (int i=2; i*i<=n; ++i)
75
+
int phi = p-1, n = phi;
76
+
for (int i = 2; i * i <= n; ++i) {
74
77
if (n % i == 0) {
75
-
fact.push_back(i);
78
+
fact.push_back(i);
76
79
while (n % i == 0)
77
80
n /= i;
78
81
}
82
+
}
79
83
if (n > 1)
80
-
fact.push_back(n);
84
+
fact.push_back(n);
81
85
82
-
for (int res=2; res<=p; ++res) {
86
+
for (int res = 2; res <= p; ++res) {
83
87
bool ok = true;
84
-
for (size_t i=0; i<fact.size() && ok; ++i)
85
-
ok &= powmod (res, phi / fact[i], p) != 1;
86
-
if (ok) return res;
88
+
for (int factor : fact) {
89
+
if (powmod(res, phi / factor, p) != 1) {
90
+
ok = false;
91
+
break;
92
+
}
93
+
}
94
+
if (ok) return res;
87
95
}
88
96
return -1;
89
97
}
90
98
99
+
// This program finds all numbers x such that x^k = a (mod n)
Copy file name to clipboardExpand all lines: src/algebra/fft.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -80,7 +80,7 @@ $$A(x) = A_0(x^2) + x A_1(x^2).$$
80
80
The polynomials $A_0$ and $A_1$ are only half as much coefficients as the polynomial $A$.
81
81
If we can compute the $\text{DFT}(A)$ in linear time using $\text{DFT}(A_0)$ and $\text{DFT}(A_1)$, then we get the recurrence $T_{\text{DFT}}(n) = 2 T_{\text{DFT}}\left(\frac{n}{2}\right) + O(n)$ for the time complexity, which results in $T_{\text{DFT}}(n) = O(n \log n)$ by the **master theorem**.
82
82
83
-
Lets learn how we can accomplish that.
83
+
Let's learn how we can accomplish that.
84
84
85
85
Suppose we have computed the vectors $\left(y_k^0\right)\_{k=0}^{n/2-1} = \text{DFT}(A_0)$ and $\left(y_k^1\right)\_{k=0}^{n/2-1} = \text{DFT}(A_1)$.
86
86
Let us find a expression for $\left(y_k\right)_{k=0}^{n-1} = \text{DFT}(A)$.
This formula is difficult to apply, but we can use it to analyze the behavior of $x^n \bmod m$. We can see that the sequence of powers $(x^1 \bmod m, x^2 \bmod m, x^3 \bmod m, \dots)$ enters a cycle of length $\phi\left(\frac{m}{a}\right)$ after the first $k$ (or less) elements.
101
101
$\phi(m)$ divides $\phi\left(\frac{m}{a}\right)$ (because $a$ and $\frac{m}{a}$ are coprime we have $\phi(a) \cdot \phi\left(\frac{m}{a}\right) = \phi(m)$), therefore we can also say that the period has length $\phi(m)$.
102
102
And since $\phi(m) \ge \log_2 m \ge k$, we can conclude the desired, much simpler, formula:
Copy file name to clipboardExpand all lines: src/data_structures/disjoint_set_union.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -347,7 +347,7 @@ The only difficulty that we face is to compute the parity in the `union_find` me
347
347
348
348
If we add an edge $(a, b)$ that connects two connected components into one, then when you attach one tree to another we need to adjust the parity.
349
349
350
-
Lets derive a formula, which computes the parity issued to the leader of the set that will get attached to another set.
350
+
Let's derive a formula, which computes the parity issued to the leader of the set that will get attached to another set.
351
351
Let $x$ be the parity of the path length from vertex $a$ up to its leader $A$, and $y$ as the parity of the path length from vertex $b$ up to its leader $B$, and $t$ the desired parity that we have to assign to $B$ after the merge.
352
352
The path contains the of the three parts:
353
353
from $B$ to $b$, from $b$ to $a$, which is connected by one edge and therefore has parity $1$, and from $a$ to $A$.
Copy file name to clipboardExpand all lines: src/data_structures/segment_tree.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -71,7 +71,7 @@ The time complexity of the construction is $O(n)$.
71
71
For now we are going to answer sum queries. As an input we receive two integers $l$ and $r$, and we have to compute the sum of the segment $a[l \dots r]$ in $O(\log n)$ time.
72
72
73
73
To do this, we will traverse the Segment Tree and use the precomputed sums of the segments.
74
-
Lets assume that we are currently at the vertex that covers the segment $a[tl \dots tr]$.
74
+
Let's assume that we are currently at the vertex that covers the segment $a[tl \dots tr]$.
75
75
There are three possible cases.
76
76
77
77
The easiest case is when the segment $a[l \dots r]$ is equal to the corresponding segment of the current vertex (i.e. $a[l \dots r] = a[tl \dots tr]$), then we are finished and can return the precomputed sum that is stored in the vertex.
We value of a flow of a network is the sum of all flows that gets produced in source $s$, or equivalent or the flows that is consumed in the sink $t$.
39
+
The value of a flow of a network is the sum of all flows that gets produced in source $s$, or equivalently of the flows that are consumed in the sink $t$.
40
40
A **maximal flow** is a flow with the maximal possible value.
41
41
Finding this maximal flow of a flow network is the problem that we want to solve.
0 commit comments