Skip to content

Commit 4bb7a0d

Browse files
authored
Merge branch 'cp-algorithms:main' into main
2 parents dddcce3 + 546ce11 commit 4bb7a0d

File tree

7 files changed

+103
-27
lines changed

7 files changed

+103
-27
lines changed

src/algebra/fibonacci-numbers.md

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Fibonacci numbers possess a lot of interesting properties. Here are a few of the
2222

2323
$$F_{n-1} F_{n+1} - F_n^2 = (-1)^n$$
2424

25+
>This can be proved by induction. A one-line proof by Knuth comes from taking the determinant of the 2x2 matrix form below.
26+
2527
* The "addition" rule:
2628

2729
$$F_{n+k} = F_k F_{n+1} + F_{k-1} F_n$$
@@ -116,11 +118,63 @@ In this way, we obtain a linear solution, $O(n)$ time, saving all the values pri
116118
117119
### Matrix form
118120
119-
It is easy to prove the following relation:
121+
To go from $(F_n, F_{n-1})$ to $(F_{n+1}, F_n)$, we can express the linear recurrence as a 2x2 matrix multiplication:
120122
121-
$$\begin{pmatrix} 1 & 1 \cr 1 & 0 \cr\end{pmatrix} ^ n = \begin{pmatrix} F_{n+1} & F_{n} \cr F_{n} & F_{n-1} \cr\end{pmatrix}$$
123+
$$
124+
\begin{pmatrix}
125+
1 & 1 \\
126+
1 & 0
127+
\end{pmatrix}
128+
\begin{pmatrix}
129+
F_n \\
130+
F_{n-1}
131+
\end{pmatrix}
132+
=
133+
\begin{pmatrix}
134+
F_n + F_{n-1} \\
135+
F_{n}
136+
\end{pmatrix}
137+
=
138+
\begin{pmatrix}
139+
F_{n+1} \\
140+
F_{n}
141+
\end{pmatrix}
142+
$$
143+
144+
This lets us treat iterating the recurrence as repeated matrix multiplication, which has nice properties. In particular,
145+
146+
$$
147+
\begin{pmatrix}
148+
1 & 1 \\
149+
1 & 0
150+
\end{pmatrix}^n
151+
\begin{pmatrix}
152+
F_1 \\
153+
F_0
154+
\end{pmatrix}
155+
=
156+
\begin{pmatrix}
157+
F_{n+1} \\
158+
F_{n}
159+
\end{pmatrix}
160+
$$
161+
162+
where $F_1 = 1, F_0 = 0$.
163+
In fact, since
164+
165+
$$
166+
\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}
167+
= \begin{pmatrix} F_2 & F_1 \\ F_1 & F_0 \end{pmatrix}
168+
$$
169+
170+
we can use the matrix directly:
171+
172+
$$
173+
\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n
174+
= \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix}
175+
$$
122176
123-
Thus, in order to find $F_n$ in $O(log n)$ time, we must raise the matrix to n. (See [Binary exponentiation](binary-exp.md))
177+
Thus, in order to find $F_n$ in $O(\log n)$ time, we must raise the matrix to n. (See [Binary exponentiation](binary-exp.md))
124178
125179
```{.cpp file=fibonacci_matrix}
126180
struct matrix {

src/geometry/manhattan-distance.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,19 @@ Here's an image to help visualizing the transformation:
8888
The Manhattan MST problem consists of, given some points in the plane, find the edges that connect all the points and have a minimum total sum of weights. The weight of an edge that connects two points is their Manhattan distance. For simplicity, we assume that all points have different locations.
8989
Here we show a way of finding the MST in $O(n \log{n})$ by finding for each point its nearest neighbor in each octant, as represented by the image below. This will give us $O(n)$ candidate edges, which, as we show below, will guarantee that they contain the MST. The final step is then using some standard MST, for example, [Kruskal algorithm using disjoint set union](https://cp-algorithms.com/graph/mst_kruskal_with_dsu.html).
9090

91-
<center>![8 octants picture](manhattan-mst-octants.png)
92-
93-
*The 8 octants relative to a point S*</center>
91+
<div style="text-align: center;">
92+
<img src="manhattan-mst-octants.png" alt="8 octants picture">
93+
*The 8 octants relative to a point S*
94+
</div>
9495

9596
The algorithm shown here was first presented in a paper from [H. Zhou, N. Shenoy, and W. Nichollos (2002)](https://ieeexplore.ieee.org/document/913303). There is also another know algorithm that uses a Divide and conquer approach by [J. Stolfi](https://www.academia.edu/15667173/On_computing_all_north_east_nearest_neighbors_in_the_L1_metric), which is also very interesting and only differ in the way they find the nearest neighbor in each octant. They both have the same complexity, but the one presented here is easier to implement and has a lower constant factor.
9697

9798
First, let's understand why it is enough to consider only the nearest neighbor in each octant. The idea is to show that for a point $s$ and any two other points $p$ and $q$ in the same octant, $d(p, q) < \max(d(s, p), d(s, q))$. This is important, because it shows that if there was a MST where $s$ is connected to both $p$ and $q$, we could erase one of these edges and add the edge $(p,q)$, which would decrease the total cost. To prove this, we assume without loss of generality that $p$ and $q$ are in the octanct $R_1$, which is defined by: $x_s \leq x$ and $x_s - y_s > x - y$, and then do some casework. The image below give some intuition on why this is true.
9899

99-
<center>![unique nearest neighbor](manhattan-mst-uniqueness.png)
100-
101-
*Intuitively, the limitation of the octant makes it impossible that $p$ and $q$ are both closer to $s$ than to each other*</center>
100+
<div style="text-align: center;">
101+
<img src="manhattan-mst-uniqueness.png" alt="unique nearest neighbor">
102+
*Intuitively, the limitation of the octant makes it impossible that $p$ and $q$ are both closer to $s$ than to each other*
103+
</div>
102104

103105

104106
Therefore, the main question is how to find the nearest neighbor in each octant for every single of the $n$ points.
@@ -109,13 +111,15 @@ For simplicity we focus on the NNE octant ($R_1$ in the image above). All other
109111

110112
We will use a sweep-line approach. We process the points from south-west to north-east, that is, by non-decreasing $x + y$. We also keep a set of points which don't have their nearest neighbor yet, which we call "active set". We add the images below to help visualize the algorithm.
111113

112-
<center>![manhattan-mst-sweep](manhattan-mst-sweep-line-1.png)
113-
114-
*In black with an arrow you can see the direction of the line-sweep. All the points below this lines are in the active set, and the points above are still not processed. In green we see the points which are in the octant of the processed point. In red the points that are not in the searched octant.*</center>
115-
116-
<center>![manhattan-mst-sweep](manhattan-mst-sweep-line-2.png)
114+
<div style="text-align: center;">
115+
<img src="manhattan-mst-sweep-line-1.png" alt="manhattan-mst-sweep">
116+
*In black with an arrow you can see the direction of the line-sweep. All the points below this lines are in the active set, and the points above are still not processed. In green we see the points which are in the octant of the processed point. In red the points that are not in the searched octant.*
117+
</div>
117118

118-
*In this image we see the active set after processing the point $p$. Note that the $2$ green points of the previous image had $p$ in its north-north-east octant and are not in the active set anymore, because they already found their nearest neighbor.*</center>
119+
<div style="text-align: center;">
120+
<img src="manhattan-mst-sweep-line-2.png" alt="manhattan-mst-sweep">
121+
*In this image we see the active set after processing the point $p$. Note that the $2$ green points of the previous image had $p$ in its north-north-east octant and are not in the active set anymore, because they already found their nearest neighbor.*
122+
</div>
119123

120124
When we add a new point point $p$, for every point $s$ that has it in its octant we can safely assign $p$ as the nearest neighbor. This is true because their distance is $d(p,s) = |x_p - x_s| + |y_p - y_s| = (x_p + y_p) - (x_s + y_s)$, because $p$ is in the north-north-east octant. As all the next points will not have a smaller value of $x + y$ because of the sorting step, $p$ is guaranteed to have the smaller distance. We can then remove all such points from the active set, and finally add $p$ to the active set.
121125

src/graph/edmonds_karp.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,23 @@ Initially we start with a flow of 0.
9090
We can find the path $s - A - B - t$ with the residual capacities 7, 5, and 8.
9191
Their minimum is 5, therefore we can increase the flow along this path by 5.
9292
This gives a flow of 5 for the network.
93-
<center>![First path](Flow2.png) ![Network after first path](Flow3.png)</center>
93+
<div style="text-align: center;">
94+
<img src="Flow2.png" alt="First path">
95+
<img src="Flow3.png" alt="Network after first path">
96+
</div>
9497

9598
Again we look for an augmenting path, this time we find $s - D - A - C - t$ with the residual capacities 4, 3, 3, and 5.
9699
Therefore we can increase the flow by 3 and we get a flow of 8 for the network.
97-
<center>![Second path](Flow4.png) ![Network after second path](Flow5.png)</center>
100+
<div style="text-align: center;">
101+
<img src="Flow4.png" alt="Second path">
102+
<img src="Flow5.png" alt="Network after second path">
103+
</div>
98104

99105
This time we find the path $s - D - C - B - t$ with the residual capacities 1, 2, 3, and 3, and hence, we increase the flow by 1.
100-
<center>![Third path](Flow6.png) ![Network after third path](Flow7.png)</center>
106+
<div style="text-align: center;">
107+
<img src="Flow6.png" alt="Third path">
108+
<img src="Flow7.png" alt="Network after third path">
109+
</div>
101110

102111
This time we find the augmenting path $s - A - D - C - t$ with the residual capacities 2, 3, 1, and 2.
103112
We can increase the flow by 1.
@@ -107,7 +116,10 @@ In the original flow network, we are not allowed to send any flow from $A$ to $D
107116
But because we already have a flow of 3 from $D$ to $A$, this is possible.
108117
The intuition of it is the following:
109118
Instead of sending a flow of 3 from $D$ to $A$, we only send 2 and compensate this by sending an additional flow of 1 from $s$ to $A$, which allows us to send an additional flow of 1 along the path $D - C - t$.
110-
<center>![Fourth path](Flow8.png) ![Network after fourth path](Flow9.png)</center>
119+
<div style="text-align: center;">
120+
<img src="Flow8.png" alt="Fourth path">
121+
<img src="Flow9.png" alt="Network after fourth path">
122+
</div>
111123

112124
Now, it is impossible to find an augmenting path between $s$ and $t$, therefore this flow of $10$ is the maximal possible.
113125
We have found the maximal flow.

src/graph/mst_prim.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ The spanning tree with the least weight is called a minimum spanning tree.
1313

1414
In the left image you can see a weighted undirected graph, and in the right image you can see the corresponding minimum spanning tree.
1515

16-
<center>![Random graph](MST_before.png) ![MST of this graph](MST_after.png)</center>
16+
<div style="text-align: center;">
17+
<img src="MST_before.png" alt="Random graph">
18+
<img src="MST_after.png" alt="MST of this graph">
19+
</div>
1720

1821
It is easy to see that any spanning tree will necessarily contain $n-1$ edges.
1922

src/graph/second_best_mst.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,13 @@ The final time complexity of this approach is $O(E \log V)$.
5353

5454
For example:
5555

56-
<center>![MST](second_best_mst_1.png) ![Second best MST](second_best_mst_2.png) <br>
56+
<div style="text-align: center;">
57+
<img src="second_best_mst_1.png" alt="MST">
58+
<img src="second_best_mst_2.png" alt="Second best MST">
59+
<br />
5760

5861
*In the image left is the MST and right is the second best MST.*
59-
</center>
62+
</div>
6063

6164

6265
In the given graph suppose we root the MST at the blue vertex on the top, and then run our algorithm by start picking the edges not in MST.

src/graph/topological-sort.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ In other words, you want to find a permutation of the vertices (**topological or
1313

1414
Here is one given graph together with its topological order:
1515

16-
<center>
17-
![example directed graph](topological_1.png)
18-
![one topological order](topological_2.png)
19-
</center>
16+
<div style="text-align: center;">
17+
<img src="topological_1.png" alt="example directed graph">
18+
<img src="topological_2.png" alt="one topological order">
19+
</div>
2020

2121
Topological order can be **non-unique** (for example, if there exist three vertices $a$, $b$, $c$ for which there exist paths from $a$ to $b$ and from $a$ to $c$ but not paths from $b$ to $c$ or from $c$ to $b$).
2222
The example graph also has multiple topological orders, a second topological order is the following:

src/string/aho_corasick.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ Assume that at the moment we stand in a vertex $v$ and consider a character $c$.
209209
1. $go[v][c] = -1$. In this case, we may assign $go[v][c] = go[u][c]$, which is already known by the induction hypothesis;
210210
2. $go[v][c] = w \neq -1$. In this case, we may assign $link[w] = go[u][c]$.
211211
212-
In this way, we spend $O(1)$ time per each pair of a vertex and a character, making the running time $O(mk)$. The major overhead here is that we copy a lot of transitions from $u$ in the first case, while the transitions of the second case form the trie and sum up to $m$ over all vertices. To avoid the copying of $go[u][c]$, we may use a persistent array data structure, using which we initially copy $go[u]$ into $go[v]$ and then only update values for characters in which the transition would differ. This leads to the $O(n \log k)$ algorithm.
212+
In this way, we spend $O(1)$ time per each pair of a vertex and a character, making the running time $O(nk)$. The major overhead here is that we copy a lot of transitions from $u$ in the first case, while the transitions of the second case form the trie and sum up to $n$ over all vertices. To avoid the copying of $go[u][c]$, we may use a persistent array data structure, using which we initially copy $go[u]$ into $go[v]$ and then only update values for characters in which the transition would differ. This leads to the $O(n \log k)$ algorithm.
213213
214214
## Applications
215215

0 commit comments

Comments
 (0)