Skip to content

Commit 6a2583f

Browse files
wikkujakobkogler
authored andcommitted
Various improvements (#396)
1 parent 13dbb11 commit 6a2583f

File tree

9 files changed

+70
-54
lines changed

9 files changed

+70
-54
lines changed

src/algebra/discrete-log.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ $$O(\sqrt {m} \log m).$$
6464

6565
### The simplest implementation
6666

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).
6870

6971
```cpp
7072
int powmod(int a, int b, int m) {
@@ -85,7 +87,7 @@ int solve(int a, int b, int m) {
8587
for (int p = n; p >= 1; --p)
8688
vals[powmod(a, p * n, m)] = p;
8789
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;
8991
if (vals.count(cur)) {
9092
int ans = vals[cur] * n - q;
9193
return ans;
@@ -106,7 +108,10 @@ We also need to change the second step accordingly.
106108
107109
## Improved implementation
108110
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.
110115
111116
```cpp
112117
int solve(int a, int b, int m) {

src/algebra/discrete-root.md

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Discrete Root
44

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:
66

77
$x^k \equiv a \pmod n$
88

@@ -14,7 +14,7 @@ Let's apply the concept of a [primitive root](./algebra/primitive-root.html) mod
1414

1515
We can easily discard the case where $a = 0$. In this case, obviously there is only one answer: $x = 0$.
1616

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:
1818

1919
$(g^y)^k \equiv a \pmod n$
2020

@@ -26,7 +26,7 @@ This, in turn, can be rewritten as
2626

2727
$(g^k)^y \equiv a \pmod n$
2828

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-step giant-step algorithm in $O(\sqrt {n} \log n)$ (or we can verify that there are no solutions).
3030

3131
Having found one solution $y_0$, one of solutions of discrete root problem will be $x_0 = g^{y_0} \pmod n$.
3232

@@ -46,88 +46,99 @@ where $l$ is chosen such that the fraction must be an integer. For this to be tr
4646

4747
$x = g^{y_0 + i \frac {\phi (n)}{gcd(k, \phi (n))}} \pmod n \forall i \in Z$.
4848

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.
5050

5151
## Implementation
5252

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.
5454

5555
```cpp
56-
int gcd (int a, int b) {
57-
return a ? gcd (b%a, a) : b;
56+
int gcd(int a, int b) {
57+
return a ? gcd(b % a, a) : b;
5858
}
5959

60-
int powmod (int a, int b, int p) {
60+
int powmod(int a, int b, int p) {
6161
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+
}
6769
return res;
6870
}
6971

70-
int generator (int p) {
72+
// Finds the primitive root modulo p
73+
int generator(int p) {
7174
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) {
7477
if (n % i == 0) {
75-
fact.push_back (i);
78+
fact.push_back(i);
7679
while (n % i == 0)
7780
n /= i;
7881
}
82+
}
7983
if (n > 1)
80-
fact.push_back (n);
84+
fact.push_back(n);
8185

82-
for (int res=2; res<=p; ++res) {
86+
for (int res = 2; res <= p; ++res) {
8387
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;
8795
}
8896
return -1;
8997
}
9098

99+
// This program finds all numbers x such that x^k = a (mod n)
91100
int main() {
92-
93101
int n, k, a;
94-
cin >> n >> k >> a;
102+
scanf("%d %d %d", &n, &k, &a);
95103
if (a == 0) {
96-
puts ("1\n0");
104+
puts("1\n0");
97105
return 0;
98106
}
99107

100-
int g = generator (n);
108+
int g = generator(n);
101109

110+
// Baby-step giant-step discrete logarithm algorithm
102111
int sq = (int) sqrt (n + .0) + 1;
103-
vector < pair<int,int> > dec (sq);
104-
for (int i=1; i<=sq; ++i)
105-
dec[i-1] = make_pair (powmod (g, int (i * sq * 1ll * k % (n - 1)), n), i);
106-
sort (dec.begin(), dec.end());
112+
vector<pair<int, int>> dec(sq);
113+
for (int i = 1; i <= sq; ++i)
114+
dec[i-1] = {powmod(g, i * sq * k % (n - 1), n), i};
115+
sort(dec.begin(), dec.end());
107116
int any_ans = -1;
108-
for (int i=0; i<sq; ++i) {
109-
int my = int (powmod (g, int (i * 1ll * k % (n - 1)), n) * 1ll * a % n);
110-
vector < pair<int,int> >::iterator it =
111-
lower_bound (dec.begin(), dec.end(), make_pair (my, 0));
117+
for (int i = 0; i < sq; ++i) {
118+
int my = powmod(g, i * k % (n - 1), n) * a % n;
119+
auto it = lower_bound(dec.begin(), dec.end(), make_pair(my, 0));
112120
if (it != dec.end() && it->first == my) {
113121
any_ans = it->second * sq - i;
114122
break;
115123
}
116124
}
117125
if (any_ans == -1) {
118-
puts ("0");
126+
puts("0");
119127
return 0;
120128
}
121129

122-
int delta = (n-1) / gcd (k, n-1);
130+
// Print all possible answers
131+
int delta = (n-1) / gcd(k, n-1);
123132
vector<int> ans;
124-
for (int cur=any_ans%delta; cur<n-1; cur+=delta)
125-
ans.push_back (powmod (g, cur, n));
126-
sort (ans.begin(), ans.end());
127-
printf ("%d\n", ans.size());
128-
for (size_t i=0; i<ans.size(); ++i)
129-
printf ("%d ", ans[i]);
130-
133+
for (int cur = any_ans % delta; cur < n-1; cur += delta)
134+
ans.push_back(powmod(g, cur, n));
135+
sort(ans.begin(), ans.end());
136+
printf("%d\n", ans.size());
137+
for (int answer : ans)
138+
printf("%d ", answer);
131139
}
132140
```
133141
142+
## Practice problems
143+
144+
* [Codeforces - Lunar New Year and a Recursive Sequence](https://codeforces.com/contest/1106/problem/F)

src/algebra/fft.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ $$A(x) = A_0(x^2) + x A_1(x^2).$$
8080
The polynomials $A_0$ and $A_1$ are only half as much coefficients as the polynomial $A$.
8181
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**.
8282

83-
Lets learn how we can accomplish that.
83+
Let's learn how we can accomplish that.
8484

8585
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)$.
8686
Let us find a expression for $\left(y_k\right)_{k=0}^{n-1} = \text{DFT}(A)$.

src/algebra/phi-function.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ This allows computing $x^n \bmod m$ for very big $n$, especially if $n$ is the r
7575
7676
There is a less known version of the last equivalence, that allows computing $x^n \bmod m$ efficiently for not coprime $x$ and $m$.
7777
For arbitrary $x, m$ and $n \geq \log_2 m$:
78-
$$x^{n}\equiv x^{\varphi(m)+[n \bmod \varphi(m)]} \mod m$$
78+
$$x^{n}\equiv x^{\phi(m)+[n \bmod \phi(m)]} \mod m$$
7979
8080
Proof:
8181
@@ -100,7 +100,7 @@ $$x^n \bmod m = x^k\left(x^{n-k \bmod \phi(\frac{m}{a})} \bmod \frac{m}{a}\right
100100
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.
101101
$\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)$.
102102
And since $\phi(m) \ge \log_2 m \ge k$, we can conclude the desired, much simpler, formula:
103-
$$ x^n \equiv x^{\varphi(m)} x^{(n - \varphi(m)) \bmod \varphi(m)} \bmod m \equiv x^{\varphi(m)+[n \bmod \varphi(m)]} \mod m.$$
103+
$$ x^n \equiv x^{\phi(m)} x^{(n - \phi(m)) \bmod \phi(m)} \bmod m \equiv x^{\phi(m)+[n \bmod \phi(m)]} \mod m.$$
104104
105105
## Practice Problems
106106

src/data_structures/disjoint_set_union.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ The only difficulty that we face is to compute the parity in the `union_find` me
347347
348348
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.
349349
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.
351351
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.
352352
The path contains the of the three parts:
353353
from $B$ to $b$, from $b$ to $a$, which is connected by one edge and therefore has parity $1$, and from $a$ to $A$.

src/data_structures/segment_tree.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ The time complexity of the construction is $O(n)$.
7171
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.
7272

7373
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]$.
7575
There are three possible cases.
7676

7777
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.

src/graph/2SAT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ We now construct a directed graph of these implications:
2222
for each variable $x$ there will be two vertices $v_x$ and $v_{\lnot x}$.
2323
The edges will correspond to the implications.
2424

25-
Lets look at the example in 2-CNF form:
25+
Let's look at the example in 2-CNF form:
2626

2727
$$(a \lor \lnot b) \land (\lnot a \lor b) \land (\lnot a \lor \lnot b) \land (a \lor \lnot c)$$
2828

src/graph/edmonds_karp.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ The following image show a flow network.
3636
The first value of each edge represents the flow, which is initially 0, and the second value represents the capacity.
3737
<center>![Flow network](&imgroot&/Flow1.png)</center>
3838

39-
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$.
4040
A **maximal flow** is a flow with the maximal possible value.
4141
Finding this maximal flow of a flow network is the problem that we want to solve.
4242

src/graph/fixed_length_paths.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ It is obvious that the constructed adjacency matrix if the answer to the problem
2020
It contains the number of paths of length $1$ between each pair of vertices.
2121

2222
We will build the solution iteratively:
23-
Lets assume we know the answer for some $k$.
23+
Let's assume we know the answer for some $k$.
2424
Here we describe a method how we can construct the answer for $k + 1$.
2525
Denote by $C_k$ the matrix for the case $k$, and by $C_{k+1}$ the matrix we want to construct.
2626
With the following formula we can compute every entry of $C_{k+1}$:

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy