diff --git a/README.md b/README.md index 513e55f32..f2383c91d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Compiled pages are published at [https://cp-algorithms.com/](https://cp-algorith ## Changelog +- August, 2025: Overhaul of CP-Algorithms [donation system](https://github.com/sponsors/cp-algorithms). Please consider supporting us, so that we can grow! +- August, 2025: Launched a [Discord server](https://discord.gg/HZ5AecN3KX)! - October, 2024: Welcome new maintainers: [jxu](https://github.com/jxu), [mhayter](https://github.com/mhayter) and [kostero](https://github.com/kostero)! - October, 15, 2024: GitHub pages based mirror is now served at [https://gh.cp-algorithms.com/](https://gh.cp-algorithms.com/), and an auxiliary competitive programming library is available at [https://lib.cp-algorithms.com/](https://lib.cp-algorithms.com/). - July 16, 2024: Major overhaul of the [Finding strongly connected components / Building condensation graph](https://cp-algorithms.com/graph/strongly-connected-components.html) article. @@ -29,6 +31,7 @@ Compiled pages are published at [https://cp-algorithms.com/](https://cp-algorith ### New articles +- (19 August 2025) [Minimum Enclosing Circle](https://cp-algorithms.com/geometry/enclosing-circle.html) - (21 May 2025) [Simulated Annealing](https://cp-algorithms.com/num_methods/simulated_annealing.html) - (12 July 2024) [Manhattan distance](https://cp-algorithms.com/geometry/manhattan-distance.html) - (8 June 2024) [Knapsack Problem](https://cp-algorithms.com/dynamic_programming/knapsack.html) diff --git a/mkdocs.yml b/mkdocs.yml index 466463564..f1b6fcbc6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,6 +31,7 @@ edit_uri: edit/main/src/ copyright: Text is available under the Creative Commons Attribution Share Alike 4.0 International License
Copyright © 2014 - 2025 by cp-algorithms contributors extra_javascript: - javascript/config.js + - javascript/donation-banner.js - https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6 - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js extra_css: @@ -79,6 +80,13 @@ plugins: - rss extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/cp-algorithms/cp-algorithms + - icon: fontawesome/brands/discord + link: https://discord.gg/HZ5AecN3KX + - icon: fontawesome/solid/circle-dollar-to-slot + link: https://github.com/sponsors/cp-algorithms analytics: provider: google property: G-7FLS2HCYHH diff --git a/src/algebra/big-integer.md b/src/algebra/big-integer.md index 7f8dd1816..72d137983 100644 --- a/src/algebra/big-integer.md +++ b/src/algebra/big-integer.md @@ -30,7 +30,7 @@ To improve performance we'll use $10^9$ as the base, so that each "digit" of the const int base = 1000*1000*1000; ``` -Digits will be stored in order from least to most significant. All operations will be implemented so that after each of them the result doesn't have any leading zeros, as long as operands didn't have any leading zeros either. All operations which might result in a number with leading zeros should be followed by code which removes them. Note that in this representation there are two valid notations for number zero: and empty vector, and a vector with a single zero digit. +Digits will be stored in order from least to most significant. All operations will be implemented so that after each of them the result doesn't have any leading zeros, as long as operands didn't have any leading zeros either. All operations which might result in a number with leading zeros should be followed by code which removes them. Note that in this representation there are two valid notations for number zero: an empty vector, and a vector with a single zero digit. ### Output diff --git a/src/algebra/binary-exp.md b/src/algebra/binary-exp.md index 52dfd27a5..99c12a30a 100644 --- a/src/algebra/binary-exp.md +++ b/src/algebra/binary-exp.md @@ -177,7 +177,7 @@ a_{31} & a_ {32} & a_ {33} & a_ {34} \\ a_{41} & a_ {42} & a_ {43} & a_ {44} \end{pmatrix}$$ -that, when multiplied by a vector with the old coordinates and an unit gives a new vector with the new coordinates and an unit: +that, when multiplied by a vector with the old coordinates and a unit gives a new vector with the new coordinates and a unit: $$\begin{pmatrix} x & y & z & 1 \end{pmatrix} \cdot \begin{pmatrix} diff --git a/src/algebra/chinese-remainder-theorem.md b/src/algebra/chinese-remainder-theorem.md index dfbdbd840..5fb6b73fe 100644 --- a/src/algebra/chinese-remainder-theorem.md +++ b/src/algebra/chinese-remainder-theorem.md @@ -154,7 +154,7 @@ $$\left\{\begin{align} a & \equiv 2 \pmod{6} \end{align}\right.$$ -It is pretty simple to determine is a system has a solution. +It is pretty simple to determine if a system has a solution. And if it has one, we can use the original algorithm to solve a slightly modified system of congruences. A single congruence $a \equiv a_i \pmod{m_i}$ is equivalent to the system of congruences $a \equiv a_i \pmod{p_j^{n_j}}$ where $p_1^{n_1} p_2^{n_2}\cdots p_k^{n_k}$ is the prime factorization of $m_i$. diff --git a/src/algebra/discrete-log.md b/src/algebra/discrete-log.md index dd899fb25..de93bdea7 100644 --- a/src/algebra/discrete-log.md +++ b/src/algebra/discrete-log.md @@ -129,7 +129,7 @@ With this change, the complexity of the algorithm is still the same, but now the 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. Problems often ask for the minimum $x$ which satisfies the solution. -It is possible to get all answers and take the minimum, or reduce the first found answer using [Euler's theorem](phi-function.md#toc-tgt-2), but we can be smart about the order in which we calculate values and ensure the first answer we find is the minimum. +It is possible to get all answers and take the minimum, or reduce the first found answer using [Euler's theorem](phi-function.md#application), but we can be smart about the order in which we calculate values and ensure the first answer we find is the minimum. ```{.cpp file=discrete_log} // Returns minimum x for which a ^ x % m = b % m, a and m are coprime. diff --git a/src/algebra/linear-diophantine-equation.md b/src/algebra/linear-diophantine-equation.md index 3f4ede86c..d70d19d26 100644 --- a/src/algebra/linear-diophantine-equation.md +++ b/src/algebra/linear-diophantine-equation.md @@ -63,7 +63,7 @@ $$a x_g + b y_g = g$$ If $c$ is divisible by $g = \gcd(a, b)$, then the given Diophantine equation has a solution, otherwise it does not have any solution. The proof is straight-forward: a linear combination of two numbers is divisible by their common divisor. -Now supposed that $c$ is divisible by $g$, then we have: +Now suppose that $c$ is divisible by $g$, then we have: $$a \cdot x_g \cdot \frac{c}{g} + b \cdot y_g \cdot \frac{c}{g} = c$$ diff --git a/src/algebra/polynomial.md b/src/algebra/polynomial.md index cd0de655f..b9baa9858 100644 --- a/src/algebra/polynomial.md +++ b/src/algebra/polynomial.md @@ -192,7 +192,7 @@ This algorithm was mentioned in [Schönhage's article](http://algo.inria.fr/semi $$A^{-1}(x) \equiv \frac{1}{A(x)} \equiv \frac{A(-x)}{A(x)A(-x)} \equiv \frac{A(-x)}{T(x^2)} \pmod{x^k}$$ -Note that $T(x)$ can be computed with a single multiplication, after which we're only interested in the first half of coefficients of its inverse series. This effectively reduces the initial problem of computing $A^{-1} \pmod{x^k}$ to computing $T^{-1} \pmod{x^{\lfloor k / 2 \rfloor}}$. +Note that $T(x)$ can be computed with a single multiplication, after which we're only interested in the first half of coefficients of its inverse series. This effectively reduces the initial problem of computing $A^{-1} \pmod{x^k}$ to computing $T^{-1} \pmod{x^{\lceil k / 2 \rceil}}$. The complexity of this method can be estimated as diff --git a/src/combinatorics/burnside.md b/src/combinatorics/burnside.md index 894b1e87d..6df2bd19f 100644 --- a/src/combinatorics/burnside.md +++ b/src/combinatorics/burnside.md @@ -271,3 +271,13 @@ int solve(int n, int m) { * [CSES - Counting Grids](https://cses.fi/problemset/task/2210) * [Codeforces - Buildings](https://codeforces.com/gym/101873/problem/B) * [CS Academy - Cube Coloring](https://csacademy.com/contest/beta-round-8/task/cube-coloring/) +* [Codeforces - Side Transmutations](https://codeforces.com/contest/1065/problem/E) +* [LightOJ - Necklace](https://vjudge.net/problem/LightOJ-1419) +* [POJ - Necklace of Beads](http://poj.org/problem?id=1286) +* [CodeChef - Lucy and Flowers](https://www.codechef.com/problems/DECORATE) +* [HackerRank - Count the Necklaces](https://www.hackerrank.com/contests/infinitum12/challenges/count-the-necklaces) +* [POJ - Magic Bracelet](http://poj.org/problem?id=2888) +* [SPOJ - Sorting Machine](https://www.spoj.com/problems/SRTMACH/) +* [Project Euler - Pizza Toppings](https://projecteuler.net/problem=281) +* [ICPC 2011 SERCP - Alphabet Soup](https://basecamp.eolymp.com/tr/problems/3064) +* [GCPC 2017 - Buildings](https://basecamp.eolymp.com/en/problems/11615) diff --git a/src/data_structures/stack_queue_modification.md b/src/data_structures/stack_queue_modification.md index fbce47929..5fad2021c 100644 --- a/src/data_structures/stack_queue_modification.md +++ b/src/data_structures/stack_queue_modification.md @@ -11,7 +11,7 @@ first we will modify a stack in a way that allows us to find the smallest elemen ## Stack modification -We want to modify the stack data structure in such a way, that it possible to find the smallest element in the stack in $O(1)$ time, while maintaining the same asymptotic behavior for adding and removing elements from the stack. +We want to modify the stack data structure in such a way, that it is possible to find the smallest element in the stack in $O(1)$ time, while maintaining the same asymptotic behavior for adding and removing elements from the stack. Quick reminder, on a stack we only add and remove elements on one end. To do this, we will not only store the elements in the stack, but we will store them in pairs: the element itself and the minimum in the stack starting from this element and below. diff --git a/src/data_structures/treap.md b/src/data_structures/treap.md index 05eeb4bca..11ff00482 100644 --- a/src/data_structures/treap.md +++ b/src/data_structures/treap.md @@ -92,7 +92,7 @@ Alternatively, insert can be done by splitting the initial treap on $X$ and doin -Implementation of **Erase ($X$)** is also clear. First we descend in the tree (as in a regular binary search tree by $X$), looking for the element we want to delete. Once the node is found, we call **Merge** on it children and put the return value of the operation in the place of the element we're deleting. +Implementation of **Erase ($X$)** is also clear. First we descend in the tree (as in a regular binary search tree by $X$), looking for the element we want to delete. Once the node is found, we call **Merge** on its children and put the return value of the operation in the place of the element we're deleting. Alternatively, we can factor out the subtree holding $X$ with $2$ split operations and merge the remaining treaps (see the picture). diff --git a/src/dynamic_programming/intro-to-dp.md b/src/dynamic_programming/intro-to-dp.md index 8bc33fd82..562a46c77 100644 --- a/src/dynamic_programming/intro-to-dp.md +++ b/src/dynamic_programming/intro-to-dp.md @@ -132,9 +132,9 @@ One of the tricks to getting better at dynamic programming is to study some of t ## Classic Dynamic Programming Problems | Name | Description/Example | | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0-1 Knapsack | Given $W$, $N$, and $N$ items with weights $w_i$ and values $v_i$, what is the maximum $\sum_{i=1}^{k} v_i$ for each subset of items of size $k$ ($1 \le k \le N$) while ensuring $\sum_{i=1}^{k} w_i \le W$? | +| [0-1 Knapsack](../dynamic_programming/knapsack.md) | Given $W$, $N$, and $N$ items with weights $w_i$ and values $v_i$, what is the maximum $\sum_{i=1}^{k} v_i$ for each subset of items of size $k$ ($1 \le k \le N$) while ensuring $\sum_{i=1}^{k} w_i \le W$? | | Subset Sum | Given $N$ integers and $T$, determine whether there exists a subset of the given set whose elements sum up to the $T$. | -| Longest Increasing Subsequence (LIS) | You are given an array containing $N$ integers. Your task is to determine the LIS in the array, i.e., a subsequence where every element is larger than the previous one. | +| [Longest Increasing Subsequence (LIS)](../dynamic_programming/longest_increasing_subsequence.md) | You are given an array containing $N$ integers. Your task is to determine the LIS in the array, i.e., a subsequence where every element is larger than the previous one. | | Counting Paths in a 2D Array | Given $N$ and $M$, count all possible distinct paths from $(1,1)$ to $(N, M)$, where each step is either from $(i,j)$ to $(i+1,j)$ or $(i,j+1)$. | | Longest Common Subsequence | You are given strings $s$ and $t$. Find the length of the longest string that is a subsequence of both $s$ and $t$. | | Longest Path in a Directed Acyclic Graph (DAG) | Finding the longest path in Directed Acyclic Graph (DAG). | @@ -143,7 +143,7 @@ One of the tricks to getting better at dynamic programming is to study some of t | Edit Distance | The edit distance between two strings is the minimum number of operations required to transform one string into the other. Operations are ["Add", "Remove", "Replace"] | ## Related Topics -* Bitmask Dynamic Programming +* [Bitmask Dynamic Programming](../dynamic_programming/profile-dynamics.md) * Digit Dynamic Programming * Dynamic Programming on Trees diff --git a/src/dynamic_programming/longest_increasing_subsequence.md b/src/dynamic_programming/longest_increasing_subsequence.md new file mode 100644 index 000000000..a4c8f0fe4 --- /dev/null +++ b/src/dynamic_programming/longest_increasing_subsequence.md @@ -0,0 +1,360 @@ +--- +tags: + - Translated +e_maxx_link: longest_increasing_subseq_log +--- + +# Longest increasing subsequence + +We are given an array with $n$ numbers: $a[0 \dots n-1]$. +The task is to find the longest, strictly increasing, subsequence in $a$. + +Formally we look for the longest sequence of indices $i_1, \dots i_k$ such that + +$$i_1 < i_2 < \dots < i_k,\quad +a[i_1] < a[i_2] < \dots < a[i_k]$$ + +In this article we discuss multiple algorithms for solving this task. +Also we will discuss some other problems, that can be reduced to this problem. + +## Solution in $O(n^2)$ with dynamic programming {data-toc-label="Solution in O(n^2) with dynamic programming"} + +Dynamic programming is a very general technique that allows to solve a huge class of problems. +Here we apply the technique for our specific task. + +First we will search only for the **length** of the longest increasing subsequence, and only later learn how to restore the subsequence itself. + +### Finding the length + +To accomplish this task, we define an array $d[0 \dots n-1]$, where $d[i]$ is the length of the longest increasing subsequence that ends in the element at index $i$. + +!!! example + + $$\begin{array}{ll} + a &= \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\} \\ + d &= \{1, 1, 2, 3, 3, 1, 1, 4, 5, 2\} + \end{array}$$ + + The longest increasing subsequence that ends at index 4 is $\{3, 4, 5\}$ with a length of 3, the longest ending at index 8 is either $\{3, 4, 5, 7, 9\}$ or $\{3, 4, 6, 7, 9\}$, both having length 5, and the longest ending at index 9 is $\{0, 1\}$ having length 2. + +We will compute this array gradually: first $d[0]$, then $d[1]$, and so on. +After this array is computed, the answer to the problem will be the maximum value in the array $d[]$. + +So let the current index be $i$. +I.e. we want to compute the value $d[i]$ and all previous values $d[0], \dots, d[i-1]$ are already known. +Then there are two options: + +- $d[i] = 1$: the required subsequence consists only of the element $a[i]$. + +- $d[i] > 1$: The subsequence will end at $a[i]$, and right before it will be some number $a[j]$ with $j < i$ and $a[j] < a[i]$. + + It's easy to see, that the subsequence ending in $a[j]$ will itself be one of the longest increasing subsequences that ends in $a[j]$. + The number $a[i]$ just extends that longest increasing subsequence by one number. + + Therefore, we can just iterate over all $j < i$ with $a[j] < a[i]$, and take the longest sequence that we get by appending $a[i]$ to the longest increasing subsequence ending in $a[j]$. + The longest increasing subsequence ending in $a[j]$ has length $d[j]$, extending it by one gives the length $d[j] + 1$. + + $$d[i] = \max_{\substack{j < i \\\\ a[j] < a[i]}} \left(d[j] + 1\right)$$ + +If we combine these two cases we get the final answer for $d[i]$: + +$$d[i] = \max\left(1, \max_{\substack{j < i \\\\ a[j] < a[i]}} \left(d[j] + 1\right)\right)$$ + +### Implementation + +Here is an implementation of the algorithm described above, which computes the length of the longest increasing subsequence. + +```{.cpp file=lis_n2} +int lis(vector const& a) { + int n = a.size(); + vector d(n, 1); + for (int i = 0; i < n; i++) { + for (int j = 0; j < i; j++) { + if (a[j] < a[i]) + d[i] = max(d[i], d[j] + 1); + } + } + + int ans = d[0]; + for (int i = 1; i < n; i++) { + ans = max(ans, d[i]); + } + return ans; +} +``` + +### Restoring the subsequence + +So far we only learned how to find the length of the subsequence, but not how to find the subsequence itself. + +To be able to restore the subsequence we generate an additional auxiliary array $p[0 \dots n-1]$ that we will compute alongside the array $d[]$. +$p[i]$ will be the index $j$ of the second last element in the longest increasing subsequence ending in $i$. +In other words the index $p[i]$ is the same index $j$ at which the highest value $d[i]$ was obtained. +This auxiliary array $p[]$ points in some sense to the ancestors. + +Then to derive the subsequence, we just start at the index $i$ with the maximal $d[i]$, and follow the ancestors until we deduced the entire subsequence, i.e. until we reach the element with $d[i] = 1$. + +### Implementation of restoring + +We will change the code from the previous sections a little bit. +We will compute the array $p[]$ alongside $d[]$, and afterwards compute the subsequence. + +For convenience we originally assign the ancestors with $p[i] = -1$. +For elements with $d[i] = 1$, the ancestors value will remain $-1$, which will be slightly more convenient for restoring the subsequence. + +```{.cpp file=lis_n2_restore} +vector lis(vector const& a) { + int n = a.size(); + vector d(n, 1), p(n, -1); + for (int i = 0; i < n; i++) { + for (int j = 0; j < i; j++) { + if (a[j] < a[i] && d[i] < d[j] + 1) { + d[i] = d[j] + 1; + p[i] = j; + } + } + } + + int ans = d[0], pos = 0; + for (int i = 1; i < n; i++) { + if (d[i] > ans) { + ans = d[i]; + pos = i; + } + } + + vector subseq; + while (pos != -1) { + subseq.push_back(a[pos]); + pos = p[pos]; + } + reverse(subseq.begin(), subseq.end()); + return subseq; +} +``` + +### Alternative way of restoring the subsequence + +It is also possible to restore the subsequence without the auxiliary array $p[]$. +We can simply recalculate the current value of $d[i]$ and also see how the maximum was reached. + +This method leads to a slightly longer code, but in return we save some memory. + +## Solution in $O(n \log n)$ with dynamic programming and binary search {data-toc-label="Solution in O(n log n) with dynamic programming and binary search"} + +In order to obtain a faster solution for the problem, we construct a different dynamic programming solution that runs in $O(n^2)$, and then later improve it to $O(n \log n)$. + +We will use the dynamic programming array $d[0 \dots n]$. +This time $d[l]$ doesn't corresponds to the element $a[i]$ or to an prefix of the array. +$d[l]$ will be the smallest element at which an increasing subsequence of length $l$ ends. + +Initially we assume $d[0] = -\infty$ and for all other lengths $d[l] = \infty$. + +We will again gradually process the numbers, first $a[0]$, then $a[1]$, etc, and in each step maintain the array $d[]$ so that it is up to date. + +!!! example + + Given the array $a = \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\}$, here are all their prefixes and their dynamic programming array. + Notice, that the values of the array don't always change at the end. + + $$ + \begin{array}{ll} + \text{prefix} = \{\} &\quad d = \{-\infty, \infty, \dots\}\\ + \text{prefix} = \{8\} &\quad d = \{-\infty, 8, \infty, \dots\}\\ + \text{prefix} = \{8, 3\} &\quad d = \{-\infty, 3, \infty, \dots\}\\ + \text{prefix} = \{8, 3, 4\} &\quad d = \{-\infty, 3, 4, \infty, \dots\}\\ + \text{prefix} = \{8, 3, 4, 6\} &\quad d = \{-\infty, 3, 4, 6, \infty, \dots\}\\ + \text{prefix} = \{8, 3, 4, 6, 5\} &\quad d = \{-\infty, 3, 4, 5, \infty, \dots\}\\ + \text{prefix} = \{8, 3, 4, 6, 5, 2\} &\quad d = \{-\infty, 2, 4, 5, \infty, \dots \}\\ + \text{prefix} = \{8, 3, 4, 6, 5, 2, 0\} &\quad d = \{-\infty, 0, 4, 5, \infty, \dots \}\\ + \text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7\} &\quad d = \{-\infty, 0, 4, 5, 7, \infty, \dots \}\\ + \text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7, 9\} &\quad d = \{-\infty, 0, 4, 5, 7, 9, \infty, \dots \}\\ + \text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\} &\quad d = \{-\infty, 0, 1, 5, 7, 9, \infty, \dots \}\\ + \end{array} + $$ + +When we process $a[i]$, we can ask ourselves. +What have the conditions to be, that we write the current number $a[i]$ into the $d[0 \dots n]$ array? + +We set $d[l] = a[i]$, if there is a longest increasing sequence of length $l$ that ends in $a[i]$, and there is no longest increasing sequence of length $l$ that ends in a smaller number. +Similar to the previous approach, if we remove the number $a[i]$ from the longest increasing sequence of length $l$, we get another longest increasing sequence of length $l -1$. +So we want to extend a longest increasing sequence of length $l - 1$ by the number $a[i]$, and obviously the longest increasing sequence of length $l - 1$ that ends with the smallest element will work the best, in other words the sequence of length $l-1$ that ends in element $d[l-1]$. + +There is a longest increasing sequence of length $l - 1$ that we can extend with the number $a[i]$, exactly if $d[l-1] < a[i]$. +So we can just iterate over each length $l$, and check if we can extend a longest increasing sequence of length $l - 1$ by checking the criteria. + +Additionally we also need to check, if we maybe have already found a longest increasing sequence of length $l$ with a smaller number at the end. +So we only update if $a[i] < d[l]$. + +After processing all the elements of $a[]$ the length of the desired subsequence is the largest $l$ with $d[l] < \infty$. + +```{.cpp file=lis_method2_n2} +int lis(vector const& a) { + int n = a.size(); + const int INF = 1e9; + vector d(n+1, INF); + d[0] = -INF; + + for (int i = 0; i < n; i++) { + for (int l = 1; l <= n; l++) { + if (d[l-1] < a[i] && a[i] < d[l]) + d[l] = a[i]; + } + } + + int ans = 0; + for (int l = 0; l <= n; l++) { + if (d[l] < INF) + ans = l; + } + return ans; +} +``` + +We now make two important observations. + +1. The array $d$ will always be sorted: + $d[l-1] < d[l]$ for all $i = 1 \dots n$. + + This is trivial, as you can just remove the last element from the increasing subsequence of length $l$, and you get a increasing subsequence of length $l-1$ with a smaller ending number. + +2. The element $a[i]$ will only update at most one value $d[l]$. + + This follows immediately from the above implementation. + There can only be one place in the array with $d[l-1] < a[i] < d[l]$. + +Thus we can find this element in the array $d[]$ using [binary search](../num_methods/binary_search.md) in $O(\log n)$. +In fact we can simply look in the array $d[]$ for the first number that is strictly greater than $a[i]$, and we try to update this element in the same way as the above implementation. + +### Implementation + +This gives us the improved $O(n \log n)$ implementation: + +```{.cpp file=lis_method2_nlogn} +int lis(vector const& a) { + int n = a.size(); + const int INF = 1e9; + vector d(n+1, INF); + d[0] = -INF; + + for (int i = 0; i < n; i++) { + int l = upper_bound(d.begin(), d.end(), a[i]) - d.begin(); + if (d[l-1] < a[i] && a[i] < d[l]) + d[l] = a[i]; + } + + int ans = 0; + for (int l = 0; l <= n; l++) { + if (d[l] < INF) + ans = l; + } + return ans; +} +``` + +### Restoring the subsequence + +It is also possible to restore the subsequence using this approach. +This time we have to maintain two auxiliary arrays. +One that tells us the index of the elements in $d[]$. +And again we have to create an array of "ancestors" $p[i]$. +$p[i]$ will be the index of the previous element for the optimal subsequence ending in element $i$. + +It's easy to maintain these two arrays in the course of iteration over the array $a[]$ alongside the computations of $d[]$. +And at the end it is not difficult to restore the desired subsequence using these arrays. + +## Solution in $O(n \log n)$ with data structures {data-toc-label="Solution in O(n log n) with data structures"} + +Instead of the above method for computing the longest increasing subsequence in $O(n \log n)$ we can also solve the problem in a different way: using some simple data structures. + +Let's go back to the first method. +Remember that $d[i]$ is the value $d[j] + 1$ with $j < i$ and $a[j] < a[i]$. + +Thus if we define an additional array $t[]$ such that + +$$t[a[i]] = d[i],$$ + +then the problem of computing the value $d[i]$ is equivalent to finding the **maximum value in a prefix** of the array $t[]$: + +$$d[i] = \max\left(t[0 \dots a[i] - 1] + 1\right)$$ + +The problem of finding the maximum of a prefix of an array (which changes) is a standard problem that can be solved by many different data structures. +For instance we can use a [Segment tree](../data_structures/segment_tree.md) or a [Fenwick tree](../data_structures/fenwick.md). + +This method has obviously some **shortcomings**: +in terms of length and complexity of the implementation this approach will be worse than the method using binary search. +In addition if the input numbers $a[i]$ are especially large, then we would have to use some tricks, like compressing the numbers (i.e. renumber them from $0$ to $n-1$), or use a dynamic segment tree (only generate the branches of the tree that are important). +Otherwise the memory consumption will be too high. + +On the other hand this method has also some **advantages**: +with this method you don't have to think about any tricky properties in the dynamic programming solution. +And this approach allows us to generalize the problem very easily (see below). + +## Related tasks + +Here are several problems that are closely related to the problem of finding the longest increasing subsequence. + +### Longest non-decreasing subsequence + +This is in fact nearly the same problem. +Only now it is allowed to use identical numbers in the subsequence. + +The solution is essentially also nearly the same. +We just have to change the inequality signs, and make a slight modification to the binary search. + +### Number of longest increasing subsequences + +We can use the first discussed method, either the $O(n^2)$ version or the version using data structures. +We only have to additionally store in how many ways we can obtain longest increasing subsequences ending in the values $d[i]$. + +The number of ways to form a longest increasing subsequences ending in $a[i]$ is the sum of all ways for all longest increasing subsequences ending in $j$ where $d[j]$ is maximal. +There can be multiple such $j$, so we need to sum all of them. + +Using a Segment tree this approach can also be implemented in $O(n \log n)$. + +It is not possible to use the binary search approach for this task. + +### Smallest number of non-increasing subsequences covering a sequence + +For a given array with $n$ numbers $a[0 \dots n - 1]$ we have to colorize the numbers in the smallest number of colors, so that each color forms a non-increasing subsequence. + +To solve this, we notice that the minimum number of required colors is equal to the length of the longest increasing subsequence. + +**Proof**: +We need to prove the **duality** of these two problems. + +Let's denote by $x$ the length of the longest increasing subsequence and by $y$ the least number of non-increasing subsequences that form a cover. +We need to prove that $x = y$. + +It is clear that $y < x$ is not possible, because if we have $x$ strictly increasing elements, than no two can be part of the same non-increasing subsequence. +Therefore we have $y \ge x$. + +We now show that $y > x$ is not possible by contradiction. +Suppose that $y > x$. +Then we consider any optimal set of $y$ non-increasing subsequences. +We transform this in set in the following way: +as long as there are two such subsequences such that the first begins before the second subsequence, and the first sequence start with a number greater than or equal to the second, then we unhook this starting number and attach it to the beginning of second. +After a finite number of steps we have $y$ subsequences, and their starting numbers will form an increasing subsequence of length $y$. +Since we assumed that $y > x$ we reached a contradiction. + +Thus it follows that $y = x$. + +**Restoring the sequences**: +The desired partition of the sequence into subsequences can be done greedily. +I.e. go from left to right and assign the current number or that subsequence ending with the minimal number which is greater than or equal to the current one. + +## Practice Problems + +- [ACMSGURU - "North-East"](http://codeforces.com/problemsets/acmsguru/problem/99999/521) +- [Codeforces - LCIS](http://codeforces.com/problemset/problem/10/D) +- [Codeforces - Tourist](http://codeforces.com/contest/76/problem/F) +- [SPOJ - DOSA](https://www.spoj.com/problems/DOSA/) +- [SPOJ - HMLIS](https://www.spoj.com/problems/HMLIS/) +- [SPOJ - ONEXLIS](https://www.spoj.com/problems/ONEXLIS/) +- [SPOJ - SUPPER](http://www.spoj.com/problems/SUPPER/) +- [Topcoder - AutoMarket](https://community.topcoder.com/stat?c=problem_statement&pm=3937&rd=6532) +- [Topcoder - BridgeArrangement](https://community.topcoder.com/stat?c=problem_statement&pm=2967&rd=5881) +- [Topcoder - IntegerSequence](https://community.topcoder.com/stat?c=problem_statement&pm=5922&rd=8075) +- [UVA - Back To Edit Distance](https://onlinejudge.org/external/127/12747.pdf) +- [UVA - Happy Birthday](https://onlinejudge.org/external/120/12002.pdf) +- [UVA - Tiling Up Blocks](https://onlinejudge.org/external/11/1196.pdf) diff --git a/src/geometry/basic-geometry.md b/src/geometry/basic-geometry.md index 89acb1642..afd44b315 100644 --- a/src/geometry/basic-geometry.md +++ b/src/geometry/basic-geometry.md @@ -180,7 +180,7 @@ double angle(point2d a, point2d b) { ``` To see the next important property we should take a look at the set of points $\mathbf r$ for which $\mathbf r\cdot \mathbf a = C$ for some fixed constant $C$. -You can see that this set of points is exactly the set of points for which the projection onto $\mathbf a$ is the point $C \cdot \dfrac{\mathbf a}{|\mathbf a|}$ and they form a hyperplane orthogonal to $\mathbf a$. +You can see that this set of points is exactly the set of points for which the projection onto $\mathbf a$ is the point $C \cdot \dfrac{\mathbf a}{|\mathbf a| ^ 2}$ and they form a hyperplane orthogonal to $\mathbf a$. You can see the vector $\mathbf a$ alongside with several such vectors having same dot product with it in 2D on the picture below:
diff --git a/src/geometry/enclosing-circle.md b/src/geometry/enclosing-circle.md new file mode 100644 index 000000000..e943a2b85 --- /dev/null +++ b/src/geometry/enclosing-circle.md @@ -0,0 +1,209 @@ +--- +tags: + - Original +--- + +# Minimum Enclosing Circle + +Consider the following problem: + +!!! example "[Library Checker - Minimum Enclosing Circle](https://judge.yosupo.jp/problem/minimum_enclosing_circle)" + + You're given $n \leq 10^5$ points $p_i=(x_i, y_i)$. + + For each $p_i$, find whether it lies on the circumference of the minimum enclosing circle of $\{p_1,\dots,p_n\}$. + +Here, by the minimum enclosing circle (MEC) we mean a circle with minimum possible radius that contains all the $n$ points, inside the circle or on its boundary. This problem has a simple randomized solution that, on first glance, looks like it would run in $O(n^3)$, but actually works in $O(n)$ expected time. + +To better understand the reasoning below, we should immediately note that the solution to the problem is unique: + +??? question "Why is the MEC unique?" + + Consider the following setup: Let $r$ be the radius of the MEC. We draw a circle of radius $r$ around each of the points $p_1,\dots,p_n$. Geometrically, the centers of circles that have radius $r$ and cover all the points $p_1,\dots,p_n$ form the intersection of all $n$ circles. + + Now, if the intersection is just a single point, this already proves that it is unique. Otherwise, the intersection is a shape of non-zero area, so we can reduce $r$ by a tiny bit, and still have non-empty intersection, which contradicts the assumption that $r$ was the minimum possible radius of the enclosing circle. + + With a similar logic, we can also show the uniqueness of the MEC if we additionally demand that it passes through a given specific point $p_i$ or two points $p_i$ and $p_j$ (it is also unique because its radius uniquely defines it). + + Alternatively, we can also assume that there are two MECs, and then notice that their intersection (which contains the points $p_1,\dots,p_n$ already) must have a smaller diameter than initial circles, and thus can be covered with a smaller circle. + +## Welzl's algorithm + +For brevity, let's denote $\operatorname{mec}(p_1,\dots,p_n)$ to be the MEC of $\{p_1,\dots,p_n\}$, and let $P_i = \{p_1,\dots,p_i\}$. + +The algorithm, initially [proposed](https://doi.org/10.1007/BFb0038202) by Welzl in 1991, goes as follows: + +1. Apply a random permutation to the input sequence of points. +2. Maintain the current candidate to be the MEC $C$, starting with $C = \operatorname{mec}(p_1, p_2)$. +3. Iterate over $i=3..n$ and check if $p_i \in C$. + 1. If $p_i \in C$ it means that $C$ is the MEC of $P_i$. + 2. Otherwise, assign $C = \operatorname{mec}(p_i, p_1)$ and iterate over $j=2..i$ and check if $p_j \in C$. + 1. If $p_j \in C$, then $C$ is the MEC of $P_j$ among circles that pass through $p_i$. + 2. Otherwise, assign $C=\operatorname{mec}(p_i, p_j)$ and iterate over $k=1..j$ and check if $p_k \in C$. + 1. If $p_k \in C$, then $C$ is the MEC of $P_k$ among circles that pass through $p_i$ and $p_j$. + 2. Otherwise, $C=\operatorname{mec}(p_i,p_j,p_k)$ is the MEC of $P_k$ among circles that pass through $p_i$ and $p_j$. + +We can see that each level of nestedness here has an invariant to maintain (that $C$ is the MEC among circles that also pass through additionally given $0$, $1$ or $2$ points), and whenever the inner loop closes, its invariant becomes equivalent to the invariant of the current iteration of its parent loop. This, in turn, ensures the _correctness_ of the algorithm as a whole. + +Omitting some technical details, for now, the whole algorithm can be implemented in C++ as follows: + +```cpp +struct point {...}; + +// Is represented by 2 or 3 points on its circumference +struct mec {...}; + +bool inside(mec const& C, point p) { + return ...; +} + +// Choose some good generator of randomness for the shuffle +mt19937_64 gen(...); +mec enclosing_circle(vector &p) { + ranges::shuffle(p, gen); + auto C = mec{p[0], p[1]}; + for(int i = 0; i < n; i++) { + if(!inside(C, p[i])) { + C = mec{p[i], p[0]}; + for(int j = 0; j < i; j++) { + if(!inside(C, p[j])) { + C = mec{p[i], p[j]}; + for(int k = 0; k < j; k++) { + if(!inside(C, p[k])) { + C = mec{p[i], p[j], p[k]}; + } + } + } + } + } + } + return C; +} +``` + +Now, it is to be expected that checking that a point $p_i$ is inside the MEC of $2$ or $3$ points can be done in $O(1)$ (we will discuss this later on). But even then, the algorithm above looks as if it would take $O(n^3)$ in the worst case just because of all the nested loops. So, how come we claimed the linear expected runtime? Let's figure out! + +### Complexity analysis + +For the inner-most loop (over $k$), clearly its expected runtime is $O(j)$ operations. What about the loop over $j$? + +It only triggers the next loop if $p_j$ is on the boundary of the MEC of $P_j$ that also passes through point $i$, _and removing $p_j$ would further shrink the circle_. Of all points in $P_j$ there can only be at most $2$ points with such property, because if there are more than $2$ points from $P_j$ on the boundary, it means that after removing any of them, there will still be at least $3$ points on the boundary, sufficient to uniquely define the circle. + +In other words, after initial random shuffle, there is at most $\frac{2}{j}$ probability that we get one of the at most two unlucky points as $p_j$. Summing it up over all $j$ from $1$ to $i$, we get the expected runtime of + +$$ +\sum\limits_{j=1}^i \frac{2}{j} \cdot O(j) = O(i). +$$ + +In exactly same fashion we can now also prove that the outermost loop has expected runtime of $O(n)$. + +### Checking that a point is in the MEC of 2 or 3 points + +Let's now figure out the implementation detail of `point` and `mec`. In this problem, it turns out to be particularly useful to use [std::complex](https://codeforces.com/blog/entry/22175) as a class for points: + +```cpp +using ftype = int64_t; +using point = complex; +``` + +As a reminder, a complex number is a number of type $x+yi$, where $i^2=-1$ and $x, y \in \mathbb R$. In C++, such complex number is represented by a 2-dimensional point $(x, y)$. Complex numbers already implement basic component-wise linear operations (addition, multiplication by a real number), but also their multiplication and division carry certain geometric meaning. + +Without going in too much detail, we will note the most important property for this particular task: Multiplying two complex numbers adds up their polar angles (counted from $Ox$ counter-clockwise), and taking a conjugate (i.e. changing $z=x+yi$ into $\overline{z} = x-yi$) multiplies the polar angle with $-1$. This allows us to formulate some very simple criteria for whether a point $z$ is inside the MEC of $2$ or $3$ specific points. + +#### MEC of 2 points + +For $2$ points $a$ and $b$, their MEC is simply the circle centered at $\frac{a+b}{2}$ with the radius $\frac{|a-b|}{2}$, in other words the circle that has $ab$ as a diameter. To check if $z$ is inside this circle we simply need to check that the angle between $za$ and $zb$ is not acute. + +
+ +
+Inner angles are obtuse, external angles are acute and angles on the circumference are right +
+ +Equivalently, we need to check that + +$$ +I_0=(b-z)\overline{(a-z)} +$$ + +doesn't have a positive real coordinate (corresponding to points that have a polar angle between $-90^\circ$ and $90^\circ$). + +#### MEC of 3 points + +Adding $z$ to the triangle $abc$ will make it a quadrilateral. Consider the following expression: + +$$ +\angle azb + \angle bca +$$ + +In a [cyclic quadrilateral](https://en.wikipedia.org/wiki/Cyclic_quadrilateral), if $c$ and $z$ are from the same side of $ab$, then the angles are equal, and will ad up to $0^\circ$ when summed up signed (i.e. positive if counter-clockwise and negative if clockwise). Correspondingly, if $c$ and $z$ are on the opposite sides, the angles will add up to $180^\circ$. + +
+ +
+Adjacent inscribed angles are same, opposing angles complement to 180 degrees +
+ +In terms of complex numbers, we can note that $\angle azb$ is the polar angle of $(b-z)\overline{(a-z)}$ and $\angle bca$ is the polar angle of $(a-c)\overline{(b-c)}$. Thus, we can conclude that $\angle azb + \angle bca$ is the polar angle of + +$$ +I_1 = (b-z) \overline{(a-z)} (a-c) \overline{(b-c)} +$$ + +If the angle is $0^\circ$ or $180^\circ$, it means that the imaginary part of $I_1$ is $0$, otherwise we can deduce whether $z$ is inside or outside of the enclosing circle of $abc$ by checking the sign of the imaginary part of $I_1$. Positive imaginary part corresponds to positive angles, and negative imaginary part corresponds to negative angles. + +But which one of them means that $z$ is inside or outside of the circle? As we already noticed, having $z$ inside the circle generally increases the magnitude of $\angle azb$, while having it outside the circle decreases it. As such, we have the following 4 cases: + +1. $\angle bca > 0^\circ$, $c$ on the same side of $ab$ as $z$. Then, $\angle azb < 0^\circ$, and $\angle azb + \angle bca < 0^\circ$ for points inside the circle. +3. $\angle bca < 0^\circ$, $c$ on the same side of $ab$ as $z$. Then, $\angle azb > 0^\circ$, and $\angle azb + \angle bca > 0^\circ$ for points inside the circle. +2. $\angle bca > 0^\circ$, $c$ on the opposite side of $ab$ to $z$. Then, $\angle azb > 0^\circ$ and $\angle azb + \angle bca > 180^\circ$ for points inside the circle. +4. $\angle bca < 0^\circ$, $c$ on the opposite side of $ab$ to $z$. Then, $\angle azb < 0^\circ$ and $\angle azb + \angle bca < 180^\circ$ for points inside the circle. + +In other words, if $\angle bca$ is positive, points inside the circle will have $\angle azb + \angle bca < 0^\circ$, otherwise they will have $\angle azb + \angle bca > 0^\circ$, assuming that we normalize the angles between $-180^\circ$ and $180^\circ$. This, in turn, can be checked by the signs of imaginary parts of $I_2=(a-c)\overline{(b-c)}$ and $I_1 = I_0 I_2$. + +**Note**: As we multiply four complex numbers to get $I_1$, the intermediate coefficients can be as large as $O(A^4)$, where $A$ is the largest coordinate magnitude in the input. On the bright side, if the input is integer, both checks above can be done fully in integers. + +#### Implementation + +Now, to actually implement the check, we should first decide how to represent the MEC. As our criteria work with the points directly, a natural and efficient way to do this is to say that MEC is directly represented as a pair or triple of points that defines it: + +```cpp +using mec = variant< + array, + array +>; +``` + +Now, we can use `std::visit` to efficiently deal with both cases in accordance with criteria above: + +```cpp +/* I < 0 if z inside C, + I > 0 if z outside C, + I = 0 if z on the circumference of C */ +ftype indicator(mec const& C, point z) { + return visit([&](auto &&C) { + point a = C[0], b = C[1]; + point I0 = (b - z) * conj(a - z); + if constexpr (size(C) == 2) { + return real(I0); + } else { + point c = C[2]; + point I2 = (a - c) * conj(b - c); + point I1 = I0 * I2; + return imag(I2) < 0 ? -imag(I1) : imag(I1); + } + }, C); +} + +bool inside(mec const& C, point p) { + return indicator(C, p) <= 0; +} + +``` + +Now, we can finally ensure that everything works by submitting the problem to the Library Checker: [#308668](https://judge.yosupo.jp/submission/308668). + +## Practice problems + +- [Library Checker - Minimum Enclosing Circle](https://judge.yosupo.jp/problem/minimum_enclosing_circle) +- [BOI 2002 - Aliens](https://www.spoj.com/problems/ALIENS) \ No newline at end of file diff --git a/src/geometry/intersecting_segments.md b/src/geometry/intersecting_segments.md index ac26c8fd5..ce61cd887 100644 --- a/src/geometry/intersecting_segments.md +++ b/src/geometry/intersecting_segments.md @@ -14,7 +14,7 @@ The naive solution algorithm is to iterate over all pairs of segments in $O(n^2) ## Algorithm Let's draw a vertical line $x = -\infty$ mentally and start moving this line to the right. -In the course of its movement, this line will meet with segments, and at each time a segment intersect with our line it intersects in exactly one point (we will assume that there are no vertical segments). +In the course of its movement, this line will meet with segments, and at each time a segment intersects with our line it intersects in exactly one point (we will assume that there are no vertical segments).
sweep line and line segment intersection diff --git a/src/geometry/manhattan-distance.md b/src/geometry/manhattan-distance.md index 87514868a..86e1d0485 100644 --- a/src/geometry/manhattan-distance.md +++ b/src/geometry/manhattan-distance.md @@ -67,11 +67,11 @@ To prove this, we just need to analyze the signs of $m$ and $n$. And it's left a We may apply this equation to the Manhattan distance formula to find out that -$$d((x_1, y_1), (x_2, y_2)) = |x_1 - x_2| + |y_1 - y_2| = \text{max}(|(x_1 + y_1) - (x_2 + y_2)|, |(x_1 - y_1) - (x_2 - y_2)|).$$ +$$d((x_1, y_1), (x_2, y_2)) = |x_1 - x_2| + |y_1 - y_2| = \text{max}(|(x_1 + y_1) - (x_2 + y_2)|, |(y_1 - x_1) - (y_2 - x_2)|).$$ -The last expression in the previous equation is the [Chebyshev distance](https://en.wikipedia.org/wiki/Chebyshev_distance) of the points $(x_1 + y_1, x_1 - y_1)$ and $(x_2 + y_2, x_2 - y_2)$. This means that, after applying the transformation +The last expression in the previous equation is the [Chebyshev distance](https://en.wikipedia.org/wiki/Chebyshev_distance) of the points $(x_1 + y_1, y_1 - x_1)$ and $(x_2 + y_2, y_2 - x_2)$. This means that, after applying the transformation -$$\alpha : (x, y) \to (x + y, x - y),$$ +$$\alpha : (x, y) \to (x + y, y - x),$$ the Manhattan distance between the points $p$ and $q$ turns into the Chebyshev distance between $\alpha(p)$ and $\alpha(q)$. diff --git a/src/geometry/manhattan-mst-sweep-line-1.png b/src/geometry/manhattan-mst-sweep-line-1.png index 548f9c352..a0f15e7d8 100644 Binary files a/src/geometry/manhattan-mst-sweep-line-1.png and b/src/geometry/manhattan-mst-sweep-line-1.png differ diff --git a/src/geometry/manhattan-mst-sweep-line-2.png b/src/geometry/manhattan-mst-sweep-line-2.png index 1fc349548..599e2cd16 100644 Binary files a/src/geometry/manhattan-mst-sweep-line-2.png and b/src/geometry/manhattan-mst-sweep-line-2.png differ diff --git a/src/geometry/point-in-convex-polygon.md b/src/geometry/point-in-convex-polygon.md index 7d6e630b7..4b3b4a898 100644 --- a/src/geometry/point-in-convex-polygon.md +++ b/src/geometry/point-in-convex-polygon.md @@ -120,5 +120,6 @@ bool pointInConvexPolygon(pt point) { ``` ## Problems -[SGU253 Theodore Roosevelt](https://codeforces.com/problemsets/acmsguru/problem/99999/253) -[Codeforces 55E Very simple problem](https://codeforces.com/contest/55/problem/E) +* [SGU253 Theodore Roosevelt](https://codeforces.com/problemsets/acmsguru/problem/99999/253) +* [Codeforces 55E Very simple problem](https://codeforces.com/contest/55/problem/E) +* [Codeforces 166B Polygons](https://codeforces.com/problemset/problem/166/B) diff --git a/src/graph/bridge-searching-online.md b/src/graph/bridge-searching-online.md index 80ed25ead..1169fc146 100644 --- a/src/graph/bridge-searching-online.md +++ b/src/graph/bridge-searching-online.md @@ -57,7 +57,7 @@ When adding the next edge $(a, b)$ there can occur three situations: In this case, this edge forms a cycle along with some of the old bridges. All these bridges end being bridges, and the resulting cycle must be compressed into a new 2-edge-connected component. - Thus, in this case the number of bridges decreases by two or more. + Thus, in this case the number of bridges decreases by one or more. Consequently the whole task is reduced to the effective implementation of all these operations over the forest of 2-edge-connected components. diff --git a/src/graph/bridge-searching.md b/src/graph/bridge-searching.md index 48a52c1f6..c2d8f0ce6 100644 --- a/src/graph/bridge-searching.md +++ b/src/graph/bridge-searching.md @@ -22,7 +22,7 @@ Pick an arbitrary vertex of the graph $root$ and run [depth first search](depth- Now we have to learn to check this fact for each vertex efficiently. We'll use "time of entry into node" computed by the depth first search. -So, let $\mathtt{tin}[v]$ denote entry time for node $v$. We introduce an array $\mathtt{low}$ which will let us store the node with earliest entry time found in the DFS search that a node $v$ can reach with a single edge from itself or its descendants. $\mathtt{low}[v]$ is the minimum of $\mathtt{tin}[v]$, the entry times $\mathtt{tin}[p]$ for each node $p$ that is connected to node $v$ via a back-edge $(v, p)$ and the values of $\mathtt{low}[to]$ for each vertex $to$ which is a direct descendant of $v$ in the DFS tree: +So, let $\mathtt{tin}[v]$ denote entry time for node $v$. We introduce an array $\mathtt{low}$ which will let us store the earliest entry time of the node found in the DFS search that a node $v$ can reach with a single edge from itself or its descendants. $\mathtt{low}[v]$ is the minimum of $\mathtt{tin}[v]$, the entry times $\mathtt{tin}[p]$ for each node $p$ that is connected to node $v$ via a back-edge $(v, p)$ and the values of $\mathtt{low}[to]$ for each vertex $to$ which is a direct descendant of $v$ in the DFS tree: $$\mathtt{low}[v] = \min \left\{ \begin{array}{l} diff --git a/src/graph/depth-first-search.md b/src/graph/depth-first-search.md index f65d27dd4..8ca547a69 100644 --- a/src/graph/depth-first-search.md +++ b/src/graph/depth-first-search.md @@ -44,7 +44,7 @@ For more details check out the implementation. The required topological ordering will be the vertices sorted in descending order of exit time. - * Check whether a given graph is acyclic and find cycles in a graph. (As mentioned above by counting back edges in every connected components). + * Check whether a given graph is acyclic and find cycles in a graph. (As mentioned below by counting back edges in every connected components). * Find strongly connected components in a directed graph: diff --git a/src/graph/mst_prim.md b/src/graph/mst_prim.md index 21649f28d..8815a2630 100644 --- a/src/graph/mst_prim.md +++ b/src/graph/mst_prim.md @@ -147,7 +147,7 @@ void prim() { ``` The adjacency matrix `adj[][]` of size $n \times n$ stores the weights of the edges, and it uses the weight `INF` if there doesn't exist an edge between two vertices. -The algorithm uses two arrays: the flag `selected[]`, which indicates which vertices we already have selected, and the array `min_e[]` which stores the edge with minimal weight to an selected vertex for each not-yet-selected vertex (it stores the weight and the end vertex). +The algorithm uses two arrays: the flag `selected[]`, which indicates which vertices we already have selected, and the array `min_e[]` which stores the edge with minimal weight to a selected vertex for each not-yet-selected vertex (it stores the weight and the end vertex). The algorithm does $n$ steps, in each iteration the vertex with the smallest edge weight is selected, and the `min_e[]` of all other vertices gets updated. ### Sparse graphs: $O(m \log n)$ diff --git a/src/javascript/donation-banner.js b/src/javascript/donation-banner.js new file mode 100644 index 000000000..8c92268a5 --- /dev/null +++ b/src/javascript/donation-banner.js @@ -0,0 +1,30 @@ +document.addEventListener("DOMContentLoaded", () => { + const STORAGE_KEY = "donationBannerHiddenUntil"; + const HIDE_DAYS = 90; + + const hiddenUntil = Number(localStorage.getItem(STORAGE_KEY) || 0); + if (Date.now() < hiddenUntil) return; + + const banner = document.createElement("aside"); + banner.id = "donation-banner"; + banner.innerHTML = ` +
+

+ Please consider + + supporting us + — ad-free, volunteer-run. +

+ +
+ `; + + const content = document.querySelector("div.md-content") || document.body; + content.insertBefore(banner, content.firstChild); + + banner.querySelector(".donation-close").addEventListener("click", () => { + banner.remove(); + const until = Date.now() + HIDE_DAYS * 24 * 60 * 60 * 1000; + localStorage.setItem(STORAGE_KEY, String(until)); + }); +}); diff --git a/src/linear_algebra/linear-system-gauss.md b/src/linear_algebra/linear-system-gauss.md index 503a93b1f..ed05b5cf9 100644 --- a/src/linear_algebra/linear-system-gauss.md +++ b/src/linear_algebra/linear-system-gauss.md @@ -42,7 +42,7 @@ Strictly speaking, the method described below should be called "Gauss-Jordan", o The algorithm is a `sequential elimination` of the variables in each equation, until each equation will have only one remaining variable. If $n = m$, you can think of it as transforming the matrix $A$ to identity matrix, and solve the equation in this obvious case, where solution is unique and is equal to coefficient $b_i$. -Gaussian elimination is based on two simple transformation: +Gaussian elimination is based on two simple transformations: * It is possible to exchange two equations * Any equation can be replaced by a linear combination of that row (with non-zero coefficient), and some other rows (with arbitrary coefficients). diff --git a/src/navigation.md b/src/navigation.md index 6b7caef53..c89a21a06 100644 --- a/src/navigation.md +++ b/src/navigation.md @@ -63,6 +63,7 @@ search: - Dynamic Programming - [Introduction to Dynamic Programming](dynamic_programming/intro-to-dp.md) - [Knapsack Problem](dynamic_programming/knapsack.md) + - [Longest increasing subsequence](dynamic_programming/longest_increasing_subsequence.md) - DP optimizations - [Divide and Conquer DP](dynamic_programming/divide-and-conquer-dp.md) - [Knuth's Optimization](dynamic_programming/knuth-optimization.md) @@ -145,6 +146,7 @@ search: - [Vertical decomposition](geometry/vertical_decomposition.md) - [Half-plane intersection - S&I Algorithm in O(N log N)](geometry/halfplane-intersection.md) - [Manhattan Distance](geometry/manhattan-distance.md) + - [Minimum Enclosing Circle](geometry/enclosing-circle.md) - Graphs - Graph traversal - [Breadth First Search](graph/breadth-first-search.md) @@ -204,7 +206,6 @@ search: - Miscellaneous - Sequences - [RMQ task (Range Minimum Query - the smallest element in an interval)](sequences/rmq.md) - - [Longest increasing subsequence](sequences/longest_increasing_subsequence.md) - [Search the subsegment with the maximum/minimum sum](others/maximum_average_segment.md) - [K-th order statistic in O(N)](sequences/k-th.md) - [MEX task (Minimal Excluded element in an array)](sequences/mex.md) diff --git a/src/num_methods/binary_search.md b/src/num_methods/binary_search.md index ae9b2aed1..b78e710bb 100644 --- a/src/num_methods/binary_search.md +++ b/src/num_methods/binary_search.md @@ -16,7 +16,7 @@ The most typical problem that leads to the binary search is as follows. You're g
Binary search of the value $7$ in an array.
-The image by [AlwaysAngry](https://commons.wikimedia.org/wiki/User:AlwaysAngry) is distributed under CC BY-SA 4.0 license. +The image by AlwaysAngry is distributed under CC BY-SA 4.0 license. Now assume that we know two indices $L < R$ such that $A_L \leq k \leq A_R$. Because the array is sorted, we can deduce that $k$ either occurs among $A_L, A_{L+1}, \dots, A_R$ or doesn't occur in the array at all. If we pick an arbitrary index $M$ such that $L < M < R$ and check whether $k$ is less or greater than $A_M$. We have two possible cases: diff --git a/src/overrides/partials/content.html b/src/overrides/partials/content.html index babde6339..b70756be2 100644 --- a/src/overrides/partials/content.html +++ b/src/overrides/partials/content.html @@ -88,7 +88,7 @@

{{ page.title | d(config.site_name, true)}}

Contributors: diff --git a/src/overrides/partials/header.html b/src/overrides/partials/header.html index 967330642..ab875aef1 100644 --- a/src/overrides/partials/header.html +++ b/src/overrides/partials/header.html @@ -87,14 +87,24 @@
{% include "partials/source.html" %}
- - - {% endif %} + + + {% if "navigation.tabs.sticky" in features %} {% if "navigation.tabs" in features %} diff --git a/src/sequences/longest_increasing_subsequence.md b/src/sequences/longest_increasing_subsequence.md index dca4f020b..5e8e9df53 100644 --- a/src/sequences/longest_increasing_subsequence.md +++ b/src/sequences/longest_increasing_subsequence.md @@ -1,360 +1,3 @@ ---- -tags: - - Translated -e_maxx_link: longest_increasing_subseq_log ---- - + # Longest increasing subsequence - -We are given an array with $n$ numbers: $a[0 \dots n-1]$. -The task is to find the longest, strictly increasing, subsequence in $a$. - -Formally we look for the longest sequence of indices $i_1, \dots i_k$ such that - -$$i_1 < i_2 < \dots < i_k,\quad -a[i_1] < a[i_2] < \dots < a[i_k]$$ - -In this article we discuss multiple algorithms for solving this task. -Also we will discuss some other problems, that can be reduced to this problem. - -## Solution in $O(n^2)$ with dynamic programming {data-toc-label="Solution in O(n^2) with dynamic programming"} - -Dynamic programming is a very general technique that allows to solve a huge class of problems. -Here we apply the technique for our specific task. - -First we will search only for the **length** of the longest increasing subsequence, and only later learn how to restore the subsequence itself. - -### Finding the length - -To accomplish this task, we define an array $d[0 \dots n-1]$, where $d[i]$ is the length of the longest increasing subsequence that ends in the element at index $i$. - -!!! example - - $$\begin{array}{ll} - a &= \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\} \\ - d &= \{1, 1, 2, 3, 3, 1, 1, 4, 5, 2\} - \end{array}$$ - - The longest increasing subsequence that ends at index 4 is $\{3, 4, 5\}$ with a length of 3, the longest ending at index 8 is either $\{3, 4, 5, 7, 9\}$ or $\{3, 4, 6, 7, 9\}$, both having length 5, and the longest ending at index 9 is $\{0, 1\}$ having length 2. - -We will compute this array gradually: first $d[0]$, then $d[1]$, and so on. -After this array is computed, the answer to the problem will be the maximum value in the array $d[]$. - -So let the current index be $i$. -I.e. we want to compute the value $d[i]$ and all previous values $d[0], \dots, d[i-1]$ are already known. -Then there are two options: - -- $d[i] = 1$: the required subsequence consists only of the element $a[i]$. - -- $d[i] > 1$: The subsequence will end at $a[i]$, and right before it will be some number $a[j]$ with $j < i$ and $a[j] < a[i]$. - - It's easy to see, that the subsequence ending in $a[j]$ will itself be one of the longest increasing subsequences that ends in $a[j]$. - The number $a[i]$ just extends that longest increasing subsequence by one number. - - Therefore, we can just iterate over all $j < i$ with $a[j] < a[i]$, and take the longest sequence that we get by appending $a[i]$ to the longest increasing subsequence ending in $a[j]$. - The longest increasing subsequence ending in $a[j]$ has length $d[j]$, extending it by one gives the length $d[j] + 1$. - - $$d[i] = \max_{\substack{j < i \\\\ a[j] < a[i]}} \left(d[j] + 1\right)$$ - -If we combine these two cases we get the final answer for $d[i]$: - -$$d[i] = \max\left(1, \max_{\substack{j < i \\\\ a[j] < a[i]}} \left(d[j] + 1\right)\right)$$ - -### Implementation - -Here is an implementation of the algorithm described above, which computes the length of the longest increasing subsequence. - -```{.cpp file=lis_n2} -int lis(vector const& a) { - int n = a.size(); - vector d(n, 1); - for (int i = 0; i < n; i++) { - for (int j = 0; j < i; j++) { - if (a[j] < a[i]) - d[i] = max(d[i], d[j] + 1); - } - } - - int ans = d[0]; - for (int i = 1; i < n; i++) { - ans = max(ans, d[i]); - } - return ans; -} -``` - -### Restoring the subsequence - -So far we only learned how to find the length of the subsequence, but not how to find the subsequence itself. - -To be able to restore the subsequence we generate an additional auxiliary array $p[0 \dots n-1]$ that we will compute alongside the array $d[]$. -$p[i]$ will be the index $j$ of the second last element in the longest increasing subsequence ending in $i$. -In other words the index $p[i]$ is the same index $j$ at which the highest value $d[i]$ was obtained. -This auxiliary array $p[]$ points in some sense to the ancestors. - -Then to derive the subsequence, we just start at the index $i$ with the maximal $d[i]$, and follow the ancestors until we deduced the entire subsequence, i.e. until we reach the element with $d[i] = 1$. - -### Implementation of restoring - -We will change the code from the previous sections a little bit. -We will compute the array $p[]$ alongside $d[]$, and afterwards compute the subsequence. - -For convenience we originally assign the ancestors with $p[i] = -1$. -For elements with $d[i] = 1$, the ancestors value will remain $-1$, which will be slightly more convenient for restoring the subsequence. - -```{.cpp file=lis_n2_restore} -vector lis(vector const& a) { - int n = a.size(); - vector d(n, 1), p(n, -1); - for (int i = 0; i < n; i++) { - for (int j = 0; j < i; j++) { - if (a[j] < a[i] && d[i] < d[j] + 1) { - d[i] = d[j] + 1; - p[i] = j; - } - } - } - - int ans = d[0], pos = 0; - for (int i = 1; i < n; i++) { - if (d[i] > ans) { - ans = d[i]; - pos = i; - } - } - - vector subseq; - while (pos != -1) { - subseq.push_back(a[pos]); - pos = p[pos]; - } - reverse(subseq.begin(), subseq.end()); - return subseq; -} -``` - -### Alternative way of restoring the subsequence - -It is also possible to restore the subsequence without the auxiliary array $p[]$. -We can simply recalculate the current value of $d[i]$ and also see how the maximum was reached. - -This method leads to a slightly longer code, but in return we save some memory. - -## Solution in $O(n \log n)$ with dynamic programming and binary search {data-toc-label="Solution in O(n log n) with dynamic programming and binary search"} - -In order to obtain a faster solution for the problem, we construct a different dynamic programming solution that runs in $O(n^2)$, and then later improve it to $O(n \log n)$. - -We will use the dynamic programming array $d[0 \dots n]$. -This time $d[l]$ doesn't corresponds to the element $a[i]$ or to an prefix of the array. -$d[l]$ will be the smallest element at which an increasing subsequence of length $l$ ends. - -Initially we assume $d[0] = -\infty$ and for all other lengths $d[l] = \infty$. - -We will again gradually process the numbers, first $a[0]$, then $a[1]$, etc, and in each step maintain the array $d[]$ so that it is up to date. - -!!! example - - Given the array $a = \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\}$, here are all their prefixes and their dynamic programming array. - Notice, that the values of the array don't always change at the end. - - $$ - \begin{array}{ll} - \text{prefix} = \{\} &\quad d = \{-\infty, \infty, \dots\}\\ - \text{prefix} = \{8\} &\quad d = \{-\infty, 8, \infty, \dots\}\\ - \text{prefix} = \{8, 3\} &\quad d = \{-\infty, 3, \infty, \dots\}\\ - \text{prefix} = \{8, 3, 4\} &\quad d = \{-\infty, 3, 4, \infty, \dots\}\\ - \text{prefix} = \{8, 3, 4, 6\} &\quad d = \{-\infty, 3, 4, 6, \infty, \dots\}\\ - \text{prefix} = \{8, 3, 4, 6, 5\} &\quad d = \{-\infty, 3, 4, 5, \infty, \dots\}\\ - \text{prefix} = \{8, 3, 4, 6, 5, 2\} &\quad d = \{-\infty, 2, 4, 5, \infty, \dots \}\\ - \text{prefix} = \{8, 3, 4, 6, 5, 2, 0\} &\quad d = \{-\infty, 0, 4, 5, \infty, \dots \}\\ - \text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7\} &\quad d = \{-\infty, 0, 4, 5, 7, \infty, \dots \}\\ - \text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7, 9\} &\quad d = \{-\infty, 0, 4, 5, 7, 9, \infty, \dots \}\\ - \text{prefix} = \{8, 3, 4, 6, 5, 2, 0, 7, 9, 1\} &\quad d = \{-\infty, 0, 1, 5, 7, 9, \infty, \dots \}\\ - \end{array} - $$ - -When we process $a[i]$, we can ask ourselves. -Under what conditions should we write the current number $a[i]$ into the $d[0 \dots n]$ array? - -We set $d[l] = a[i]$, if there is a longest increasing sequence of length $l$ that ends in $a[i]$, and there is no longest increasing sequence of length $l$ that ends in a smaller number. -Similar to the previous approach, if we remove the number $a[i]$ from the longest increasing sequence of length $l$, we get another longest increasing sequence of length $l -1$. -So we want to extend a longest increasing sequence of length $l - 1$ by the number $a[i]$, and obviously the longest increasing sequence of length $l - 1$ that ends with the smallest element will work the best, in other words the sequence of length $l-1$ that ends in element $d[l-1]$. - -There is a longest increasing sequence of length $l - 1$ that we can extend with the number $a[i]$, exactly if $d[l-1] < a[i]$. -So we can just iterate over each length $l$, and check if we can extend a longest increasing sequence of length $l - 1$ by checking the criteria. - -Additionally we also need to check, if we maybe have already found a longest increasing sequence of length $l$ with a smaller number at the end. -So we only update if $a[i] < d[l]$. - -After processing all the elements of $a[]$ the length of the desired subsequence is the largest $l$ with $d[l] < \infty$. - -```{.cpp file=lis_method2_n2} -int lis(vector const& a) { - int n = a.size(); - const int INF = 1e9; - vector d(n+1, INF); - d[0] = -INF; - - for (int i = 0; i < n; i++) { - for (int l = 1; l <= n; l++) { - if (d[l-1] < a[i] && a[i] < d[l]) - d[l] = a[i]; - } - } - - int ans = 0; - for (int l = 0; l <= n; l++) { - if (d[l] < INF) - ans = l; - } - return ans; -} -``` - -We now make two important observations. - -1. The array $d$ will always be sorted: - $d[l-1] < d[l]$ for all $i = 1 \dots n$. - - This is trivial, as you can just remove the last element from the increasing subsequence of length $l$, and you get a increasing subsequence of length $l-1$ with a smaller ending number. - -2. The element $a[i]$ will only update at most one value $d[l]$. - - This follows immediately from the above implementation. - There can only be one place in the array with $d[l-1] < a[i] < d[l]$. - -Thus we can find this element in the array $d[]$ using [binary search](../num_methods/binary_search.md) in $O(\log n)$. -In fact we can simply look in the array $d[]$ for the first number that is strictly greater than $a[i]$, and we try to update this element in the same way as the above implementation. - -### Implementation - -This gives us the improved $O(n \log n)$ implementation: - -```{.cpp file=lis_method2_nlogn} -int lis(vector const& a) { - int n = a.size(); - const int INF = 1e9; - vector d(n+1, INF); - d[0] = -INF; - - for (int i = 0; i < n; i++) { - int l = upper_bound(d.begin(), d.end(), a[i]) - d.begin(); - if (d[l-1] < a[i] && a[i] < d[l]) - d[l] = a[i]; - } - - int ans = 0; - for (int l = 0; l <= n; l++) { - if (d[l] < INF) - ans = l; - } - return ans; -} -``` - -### Restoring the subsequence - -It is also possible to restore the subsequence using this approach. -This time we have to maintain two auxiliary arrays. -One that tells us the index of the elements in $d[]$. -And again we have to create an array of "ancestors" $p[i]$. -$p[i]$ will be the index of the previous element for the optimal subsequence ending in element $i$. - -It's easy to maintain these two arrays in the course of iteration over the array $a[]$ alongside the computations of $d[]$. -And at the end it is not difficult to restore the desired subsequence using these arrays. - -## Solution in $O(n \log n)$ with data structures {data-toc-label="Solution in O(n log n) with data structures"} - -Instead of the above method for computing the longest increasing subsequence in $O(n \log n)$ we can also solve the problem in a different way: using some simple data structures. - -Let's go back to the first method. -Remember that $d[i]$ is the value $d[j] + 1$ with $j < i$ and $a[j] < a[i]$. - -Thus if we define an additional array $t[]$ such that - -$$t[a[i]] = d[i],$$ - -then the problem of computing the value $d[i]$ is equivalent to finding the **maximum value in a prefix** of the array $t[]$: - -$$d[i] = \max\left(t[0 \dots a[i] - 1] + 1\right)$$ - -The problem of finding the maximum of a prefix of an array (which changes) is a standard problem that can be solved by many different data structures. -For instance we can use a [Segment tree](../data_structures/segment_tree.md) or a [Fenwick tree](../data_structures/fenwick.md). - -This method has obviously some **shortcomings**: -in terms of length and complexity of the implementation this approach will be worse than the method using binary search. -In addition if the input numbers $a[i]$ are especially large, then we would have to use some tricks, like compressing the numbers (i.e. renumber them from $0$ to $n-1$), or use a dynamic segment tree (only generate the branches of the tree that are important). -Otherwise the memory consumption will be too high. - -On the other hand this method has also some **advantages**: -with this method you don't have to think about any tricky properties in the dynamic programming solution. -And this approach allows us to generalize the problem very easily (see below). - -## Related tasks - -Here are several problems that are closely related to the problem of finding the longest increasing subsequence. - -### Longest non-decreasing subsequence - -This is in fact nearly the same problem. -Only now it is allowed to use identical numbers in the subsequence. - -The solution is essentially also nearly the same. -We just have to change the inequality signs, and make a slight modification to the binary search. - -### Number of longest increasing subsequences - -We can use the first discussed method, either the $O(n^2)$ version or the version using data structures. -We only have to additionally store in how many ways we can obtain longest increasing subsequences ending in the values $d[i]$. - -The number of ways to form a longest increasing subsequences ending in $a[i]$ is the sum of all ways for all longest increasing subsequences ending in $j$ where $d[j]$ is maximal. -There can be multiple such $j$, so we need to sum all of them. - -Using a Segment tree this approach can also be implemented in $O(n \log n)$. - -It is not possible to use the binary search approach for this task. - -### Smallest number of non-increasing subsequences covering a sequence - -For a given array with $n$ numbers $a[0 \dots n - 1]$ we have to colorize the numbers in the smallest number of colors, so that each color forms a non-increasing subsequence. - -To solve this, we notice that the minimum number of required colors is equal to the length of the longest increasing subsequence. - -**Proof**: -We need to prove the **duality** of these two problems. - -Let's denote by $x$ the length of the longest increasing subsequence and by $y$ the least number of non-increasing subsequences that form a cover. -We need to prove that $x = y$. - -It is clear that $y < x$ is not possible, because if we have $x$ strictly increasing elements, than no two can be part of the same non-increasing subsequence. -Therefore we have $y \ge x$. - -We now show that $y > x$ is not possible by contradiction. -Suppose that $y > x$. -Then we consider any optimal set of $y$ non-increasing subsequences. -We transform this in set in the following way: -as long as there are two such subsequences such that the first begins before the second subsequence, and the first sequence start with a number greater than or equal to the second, then we unhook this starting number and attach it to the beginning of second. -After a finite number of steps we have $y$ subsequences, and their starting numbers will form an increasing subsequence of length $y$. -Since we assumed that $y > x$ we reached a contradiction. - -Thus it follows that $y = x$. - -**Restoring the sequences**: -The desired partition of the sequence into subsequences can be done greedily. -I.e. go from left to right and assign the current number or that subsequence ending with the minimal number which is greater than or equal to the current one. - -## Practice Problems - -- [ACMSGURU - "North-East"](http://codeforces.com/problemsets/acmsguru/problem/99999/521) -- [Codeforces - LCIS](http://codeforces.com/problemset/problem/10/D) -- [Codeforces - Tourist](http://codeforces.com/contest/76/problem/F) -- [SPOJ - DOSA](https://www.spoj.com/problems/DOSA/) -- [SPOJ - HMLIS](https://www.spoj.com/problems/HMLIS/) -- [SPOJ - ONEXLIS](https://www.spoj.com/problems/ONEXLIS/) -- [SPOJ - SUPPER](http://www.spoj.com/problems/SUPPER/) -- [Topcoder - AutoMarket](https://community.topcoder.com/stat?c=problem_statement&pm=3937&rd=6532) -- [Topcoder - BridgeArrangement](https://community.topcoder.com/stat?c=problem_statement&pm=2967&rd=5881) -- [Topcoder - IntegerSequence](https://community.topcoder.com/stat?c=problem_statement&pm=5922&rd=8075) -- [UVA - Back To Edit Distance](https://onlinejudge.org/external/127/12747.pdf) -- [UVA - Happy Birthday](https://onlinejudge.org/external/120/12002.pdf) -- [UVA - Tiling Up Blocks](https://onlinejudge.org/external/11/1196.pdf) +This article has been moved to a [Longest increasing subsequence](../dynamic_programming/longest_increasing_subsequence.md). \ No newline at end of file diff --git a/src/string/z-function.md b/src/string/z-function.md index c83938cb4..18be20931 100644 --- a/src/string/z-function.md +++ b/src/string/z-function.md @@ -204,6 +204,7 @@ The proof for this fact is the same as the solution which uses the [prefix funct ## Practice Problems +* [CSES - Finding Borders](https://cses.fi/problemset/task/1732) * [eolymp - Blocks of string](https://www.eolymp.com/en/problems/1309) * [Codeforces - Password [Difficulty: Easy]](http://codeforces.com/problemset/problem/126/B) * [UVA # 455 "Periodic Strings" [Difficulty: Medium]](http://uva.onlinejudge.org/index.php?option=onlinejudge&page=show_problem&problem=396) diff --git a/src/stylesheets/extra.css b/src/stylesheets/extra.css index d40c3e65f..b63b3c9a9 100644 --- a/src/stylesheets/extra.css +++ b/src/stylesheets/extra.css @@ -54,4 +54,39 @@ body[dir=rtl] .metadata.page-metadata .contributors-text{ .arithmatex{ overflow-y: hidden !important; -} \ No newline at end of file +} + +/* Donation banner */ +#donation-banner { + margin: 0.75rem 0.75rem 0; +} + +.donation-banner { + display: flex; + + background: #fff9c4; + border: 1px solid #f0e68c; + color: #2b2b2b; + + padding: 0.5rem 0.5rem; + border-radius: 8px; + font-size: 0.75rem; +} + +.donation-text { margin: 0; } +.donation-link { + color: revert; + text-decoration: underline; +} + +.donation-close { + margin-left: auto; + font-size: 1rem; + cursor: pointer; +} + +[data-md-color-scheme="slate"] .donation-banner { + background: #3b3200; + border-color: #665c1e; + color: #fff7b3; +}