├── 3SUM.md ├── BFS.md ├── BFSLevels.md ├── DFS.md ├── DFSIterative.md ├── DynProgSolutions ├── BinomialCoefficient.cpp ├── DynProgProblems.h ├── IntegerPartition.cpp ├── Knapsack.cpp ├── KnapsackNoRepetitions.cpp ├── LargestCopyPasteFile.cpp ├── LargestCopyPasteFileSimple.cpp ├── LevenshteinDistance.cpp ├── LongestIncreasingSubsequence.cpp ├── Matrix.h ├── MaximumSubarraySum.cpp ├── MaximumSubmatrixSum.cpp ├── MinimumCoinCount.cpp ├── MinimumCoinCountLimited.cpp ├── MinimumDeletionsToPalindrome.cpp ├── MinimumSquareCount.cpp ├── NDigitIntegerCount.cpp ├── OptimalSingleRobotPath.cpp ├── OptimalTwoRobotPath.cpp ├── Partition.cpp ├── PictureProfit.cpp └── SingleRobotPathCount.cpp ├── DynamicProgrammingProblems.md ├── Graph.h ├── GraphAlgorithm.h ├── LinearStringSorting.md ├── LowerBounds.md ├── Product.md ├── README.md ├── UnionFind.h ├── ex03-extratasks.md └── slopes.md /3SUM.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3SUM(A[1..n] : array of different integers) 3 | 1. Sort(A[1..n]) 4 | 2. for i <- 1 to n-2 5 | 3. if A[i] >= 0 6 | 4. return 7 | 5. j <- i+1, k <- n 8 | 6. while j 3 | 4 | // T(n,k) = O(nk), M(n,k) = O(nk) <- can be reduced to M(n,k) = O(k) 5 | void binomialCoefficient(const int n, const int k) { 6 | assert(n > 0 && k > 0); 7 | Matrix M(n, k, 0); 8 | for (int i = 0; i < k; i++) M[i, i] = 1; 9 | for (int i = 0; i < n; i++) M[i, 0] = i + 1; 10 | 11 | for (int i = 1; i < n; i++) { 12 | for (int j = 1; j < k && j < i; j++) { 13 | M[i, j] = M[i - 1, j - 1] + M[i - 1, j]; 14 | } 15 | } 16 | //return M[n - 1, k - 1]; 17 | std::println("{} over {} = {}", n, k, M[n - 1, k - 1]); 18 | } 19 | -------------------------------------------------------------------------------- /DynProgSolutions/DynProgProblems.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Matrix.h" 3 | #include 4 | #include 5 | 6 | void minimumCoinCount(std::span coins, const int S); 7 | 8 | void minimumCoinCountLimited(std::span coins, const int S); 9 | 10 | void binomialCoefficient(const int n, const int k); 11 | 12 | void longestIncreasingSubsequence(std::span values); 13 | 14 | void longestCommonSubsequence(std::span v1, std::span v2); 15 | 16 | void optimalSingleRobotPath(const Matrix& field); 17 | 18 | void optimalTwoRobotPath(const Matrix& field); 19 | 20 | void singleRobotPathCount(const Matrix& blocked); 21 | 22 | void optimalMatrixMultiplication(std::span> coords); 23 | 24 | void minimumPalindromSubstrings(std::string_view str); 25 | 26 | void minimumDeletionsToPalindrome(std::string_view str); 27 | 28 | void nDigitIntegerCount(const int n, const int S); 29 | 30 | struct Item { int weight, price; }; 31 | void Knapsack(std::span items, const int W); 32 | 33 | void KnapsackNoRepetitions(std::span items, const int W); 34 | 35 | void EqualSumSubsets(std::span values); 36 | 37 | void Partition(std::span values); 38 | 39 | void LevenshteinDistance(std::string_view str1, std::string_view str2); 40 | 41 | void maximumSubarraySum(std::span values, 42 | int* result = nullptr, int* from = nullptr, int* to = nullptr); 43 | 44 | void maximumSubmatrixSum(const Matrix& values); 45 | 46 | enum class Key { A, CtrlA, CtrlC, CtrlV }; 47 | void largestCopyPasteFileSimple(const int n); 48 | 49 | void largestCopyPasteFile(const int n); 50 | 51 | void pictureProfit(std::span values); 52 | 53 | void minimumSquareCount(const int n); 54 | 55 | // Generates all integer partitions via a coroutine, with or without dupicates (by default, without). 56 | #include // Preferably from https://github.com/andreasbuhr/cppcoro 57 | cppcoro::generator> integerPartitionsGen(const int n, const bool printAll = false); 58 | /* Example usage: 59 | * for (auto part : integerPartitionsGen(n)) { 60 | * fmt::print("{}\n", fmt::join(part, "+")); 61 | * } 62 | */ 63 | -------------------------------------------------------------------------------- /DynProgSolutions/IntegerPartition.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | 3 | cppcoro::generator> integerPartitionsGen(const int n, const bool printAll) { 4 | std::vector xs(n, 0); // At most n numbers add up to n (all ones) 5 | // Initialize with the first, trivial partition [n] 6 | xs[0] = n; 7 | int pos = 1; // Index of the next number in xs to be filled 8 | int rest = 0; // Invariant: rest == n - Sum(xs[0..pos)) 9 | // Each iteration advances from one partition to the next 10 | while (true) { 11 | co_yield { xs.begin(), xs.begin() + pos }; // This is xs[0..pos) 12 | if (pos == n) { // This was the last partition: 1+...+1 13 | break; 14 | } 15 | while (xs[--pos] == 1) { // Unroll until we reach a number that can be decreased 16 | ++rest; // same as rest += xs[idx] 17 | } 18 | // Decrease one number to "switch" to the next partition 19 | --xs[pos]; 20 | ++rest; 21 | ++pos; 22 | // Fill the rest of it 23 | while (rest > 0) { 24 | xs[pos] = (printAll ? rest : std::min(rest, xs[pos - 1])); 25 | rest -= xs[pos]; 26 | ++pos; 27 | } 28 | } 29 | } 30 | 31 | // Naive, recursive solution: 32 | #include 33 | 34 | cppcoro::recursive_generator> 35 | partitionsHelper(const std::span xs, const int rest, const int pos, const bool printAll) 36 | { 37 | if (rest == 0) { 38 | co_yield xs.subspan(0, pos); 39 | } else { 40 | int first = (printAll || pos == 0 ? rest : std::min(rest, xs[pos - 1])); 41 | for (; first >= 1; --first) { 42 | xs[pos] = first; 43 | co_yield partitionsHelper(xs, rest - first, pos + 1, printAll); 44 | } 45 | } 46 | } 47 | 48 | cppcoro::recursive_generator> 49 | integerPartitionsGenR(const int n, const bool printAll = false) 50 | { 51 | if (n > 1) { 52 | std::vector xs(n); // At most n numbers add to n (all ones) 53 | co_yield partitionsHelper(xs, n, 0, printAll); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /DynProgSolutions/Knapsack.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n,W) = O(nW), M(n,W) = O(W) 5 | void Knapsack(std::span items, const int W) { 6 | assert(W >= 0); 7 | std::vector M(W + 1, 0); 8 | for (int w = 1; w <= W; ++w) { 9 | for (const Item& item : items) { 10 | if (item.weight <= w) { 11 | M[w] = std::max(M[w], M[w - item.weight] + item.price); 12 | } 13 | } 14 | } 15 | //return M[W]; 16 | 17 | std::println("The optimal profit for weight {} is: {}", W, M[W]); 18 | std::println("The following items are being used:"); 19 | int w = W; 20 | while (M[w] != 0) { 21 | for (const Item& item : items) 22 | if (item.weight <= w && M[w] == M[w - item.weight] + item.price) { 23 | std::println(" W: {} C: {}", item.weight, item.price); 24 | w -= item.weight; 25 | break; 26 | } 27 | } 28 | std::println("The unused space in the knapsack is: {}.", w); 29 | } 30 | -------------------------------------------------------------------------------- /DynProgSolutions/KnapsackNoRepetitions.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n,W) = O(nW), M(n,W) = O(nW) <- can be reduced to M(n,W) = O(W) 5 | void KnapsackNoRepetitions(std::span items, const int W) { 6 | assert(W >= 0); 7 | const int n = int(items.size()); 8 | Matrix M(n + 1, W + 1, 0); 9 | for (int k = 1; k <= n; ++k) { 10 | for (int P = 1; P <= W; ++P) { 11 | const Item& item = items[k - 1]; 12 | if (item.weight > P) { 13 | M[k, P] = M[k - 1, P]; 14 | } else { 15 | M[k, P] = std::max(M[k - 1, P], M[k - 1, P - item.weight] + item.price); 16 | } 17 | } 18 | } 19 | //return M[n, W]; 20 | 21 | std::println("The optimal profit for weight {} is: {}", W, M[n, W]); 22 | std::println("The following items are being used:"); 23 | int P = W; 24 | for (int k = n; k > 0; --k) { 25 | if (M[k, P] != M[k - 1, P]) { 26 | const Item& item = items[k - 1]; 27 | std::println(" W: {} C: {}", item.weight, item.price); 28 | P -= item.weight; 29 | } 30 | } 31 | std::println("The unused space in the knapsack is: {}.", P); 32 | } -------------------------------------------------------------------------------- /DynProgSolutions/LargestCopyPasteFile.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | #include // std::views::reverse 4 | 5 | // T(n) = O(n^2), M(n) = O(n) <- can be reduced to T(n) = O(n) 6 | void largestCopyPasteFile(const int n) { 7 | assert(n < 158); // n > 158 can lead to overflow ! 8 | // closed formula - unknown :/ 9 | std::vector M(n + 1, 0); 10 | M[1] = 1; 11 | M[2] = 2; 12 | for (int k = 3; k <= n; ++k){ 13 | M[k] = std::max(M[k - 1] + 1, M[k - 1]); 14 | for (int i = 0; i <= k - 3; i++) { // number of Ctrl-V actions after a Ctrl-C 15 | M[k] = std::max(M[k], (i + 1) * M[k - 2 - i]); 16 | } 17 | // It can be shown that i < 6 is enough for optimal strategy => T(n) = O(n) 18 | } 19 | // return M[n]; 20 | 21 | std::println("The largest possible filesize with {} keystrokes is: {}", n, M[n]); 22 | 23 | std::vector keystrokes; 24 | size_t idx = n; 25 | while (idx > 0) { 26 | if (M[idx] == M[idx - 1] + 1) { 27 | keystrokes.push_back(int(Key::A)); 28 | --idx; 29 | } else if (M[idx] == M[idx - 1]) { 30 | keystrokes.push_back(int(Key::CtrlA)); 31 | --idx; 32 | } else for (int i = 2; i < idx; ++i) { 33 | if (M[idx] == (i - 1)*M[idx - i]) { 34 | keystrokes.push_back(i); 35 | idx -= i; 36 | break; 37 | } 38 | } 39 | } 40 | 41 | for (int k : std::views::reverse(keystrokes)) { 42 | switch (k) { 43 | case int(Key::A): { std::println("\'a\'"); break; } 44 | case int(Key::CtrlA): { std::println("Ctrl-A"); break; } 45 | default: { 46 | std::print("Ctrl-A Ctrl-C"); 47 | for (int i = 2; i < k; i++) { 48 | std::print(" Ctrl-V"); 49 | } 50 | std::println(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /DynProgSolutions/LargestCopyPasteFileSimple.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | #include // std::views::reverse 4 | 5 | // T(n) = O(n), M(n) = O(n) 6 | void largestCopyPasteFileSimple(const int n) { 7 | assert(n <= 189); // n = 190 overflows uint64 8 | // Closed formula: 9 | // return ((3 + n%3) * (1ui64 << (n / 3 - 1))); 10 | // T(n) = O(1), M(n) = O(1), f(n) = Theta(2^(n/3)) 11 | 12 | std::vector M(n + 1, 0); 13 | M[1] = 1; 14 | M[2] = 2; 15 | for (int k = 3; k <= n; k++) { 16 | M[k] = std::max(std::max(M[k - 1] + 1, M[k - 1]), 17 | std::max(M[k - 2], 2 * M[k - 3])); 18 | } 19 | //return M[n]; 20 | 21 | std::println("The largest possible filesize with {} keystrokes is: {}", n, M[n]); 22 | 23 | std::vector keystrokes; 24 | size_t idx = n; 25 | while (idx > 0) { 26 | if (M[idx] == M[idx - 1] + 1) { 27 | keystrokes.push_back(Key::A); --idx; 28 | } else if (M[idx] == M[idx - 1]) { 29 | keystrokes.push_back(Key::CtrlA); --idx; 30 | } else if (M[idx] == M[idx - 2]) { 31 | keystrokes.push_back(Key::CtrlC); idx -= 2; 32 | } else /*(M[idx] == 2 * M[idx - 3])*/ { 33 | keystrokes.push_back(Key::CtrlV); idx -= 3; 34 | } 35 | } 36 | 37 | for (const Key k : std::views::reverse(keystrokes)) { 38 | switch (k) { 39 | case Key::A: { std::println("\'a\'"); break; } 40 | case Key::CtrlA: { std::println("Ctrl-A"); break; } 41 | case Key::CtrlC: { std::println("Ctrl-A Ctrl-C"); break; } 42 | case Key::CtrlV: { std::println("Ctrl-A Ctrl-C Ctrl-V"); break; } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DynProgSolutions/LevenshteinDistance.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n,m) = O(nm), M(n,m) = O(nm) <- can be reduced to M(n,m) = O(min{n,m}) 5 | void LevenshteinDistance(std::string_view str1, std::string_view str2) { 6 | // for details, see https://github.com/Andreshk/ApproximateStringMatching 7 | const int n = int(str1.size()), m = int(str2.size()); 8 | Matrix M(n + 1, m + 1); 9 | for (int i = 1; i <= n; i++) { 10 | M[i, 0] = i; 11 | } 12 | for (int j = 0; j <= m; j++) { 13 | M[0, j] = j; 14 | } 15 | for (int i = 1; i <= n; i++) { 16 | for (int j = 1; j <= m; j++) { 17 | M[i, j] = std::min({ M[i - 1, j] + 1, M[i, j - 1] + 1, 18 | M[i - 1, j - 1] + ((str1[i - 1] == str2[j - 1]) ? 0 : 1) }); 19 | } 20 | } 21 | //return M[n, m]; 22 | 23 | std::println("Minimum edit distance between \"{}\" and \"{}\" is {}.", str1, str2, M[n, m]); 24 | } 25 | -------------------------------------------------------------------------------- /DynProgSolutions/LongestIncreasingSubsequence.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | #include // std::ranges::{max,max_element} 4 | 5 | // T(n) = O(n^2), M(n) = O(n) 6 | void longestIncreasingSubsequence(std::span values) { 7 | /* Note: 8 | * building a graph leads to T(n) = O(n^2), but M(n) = O(n+m), 9 | * since the process is always slow and we need additional memory 10 | * Without a graph (using the implicit one) we have T(n) = O(n^2) 11 | * still, but M(n) = O(n) only. 12 | */ 13 | const int count = int(values.size()); 14 | std::vector M(count), successors(count); 15 | for (int i = count - 1; i >= 0; --i) { 16 | M[i] = 1; 17 | successors[i] = count; 18 | for (int j = i + 1; j < count; j++) { 19 | if (values[i] < values[j] && M[i] < M[j] + 1) { 20 | M[i] = M[j] + 1; 21 | successors[i] = j; 22 | } 23 | } 24 | } 25 | //return std::ranges::max(M); 26 | 27 | // Find the maximum value 28 | const int maxIdx = int(std::distance(M.begin(), std::ranges::max_element(M))); 29 | std::print("Longest increasing subsequence of {}\n is:", values); 30 | for (int next = maxIdx; next != count; next = successors[next]) { 31 | std::print(" {}", values[next]); 32 | } 33 | std::println("\nLength = {}", M[maxIdx]); 34 | } 35 | -------------------------------------------------------------------------------- /DynProgSolutions/Matrix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | class Matrix { 9 | std::unique_ptr values; 10 | int n, m; // row & column count, respectively 11 | 12 | using MatrixInitializerList = std::initializer_list>; 13 | void init(MatrixInitializerList il) { 14 | n = int(il.size()); 15 | m = int(il.begin()->size()); 16 | values = std::make_unique_for_overwrite(n * m); 17 | for (int idx = 0; const auto& row : il) { 18 | assert(row.size() == m); 19 | for (const auto& val : row) { 20 | values[idx++] = val; 21 | } 22 | } 23 | } 24 | public: 25 | Matrix(int _n, int _m, const T& _val = T{}) : values{std::make_unique_for_overwrite(_n*_m)}, n(_n), m(_m) { 26 | std::uninitialized_fill_n(values.get(), n * m, _val); 27 | } 28 | /* allows for the following: 29 | * Matrix m1{ {1,2,3}, {4,5,6} }; 30 | * Matrix m2 = { {1,2,3}, {4,5,6} }; 31 | * void f(Matrix); 32 | * f({ {1,2,3}, {4,5,6} }); 33 | */ 34 | Matrix(MatrixInitializerList il) { init(il); } 35 | Matrix& operator=(MatrixInitializerList il) { init(il); return *this; } 36 | T& operator[](int row, int col) { 37 | assert(row >= 0 && row < n && col >= 0 && col < m); 38 | return reinterpret_cast(values[row * m + col]); 39 | } 40 | const T& operator[](int row, int col) const { 41 | assert(row >= 0 && row < n && col >= 0 && col < m); 42 | return reinterpret_cast(values[row * m + col]); 43 | } 44 | // "Safe" alternative to operator[], returning a zero value on invalid input. 45 | T at(int i, int j) const { 46 | return ((i < 0 || j < 0 || i >= n || j >= m) ? T{} : (*this)[i, j]); 47 | } 48 | int rows() const { return n; } 49 | int cols() const { return m; } 50 | // Pointers are the poor man's iterators, but they work just fine here 51 | T* begin() { return values.get(); } 52 | const T* begin() const { return values.get(); } 53 | T* end() { return values.get() + n*m; } 54 | const T* end() const { return values.get() + n*m; } 55 | }; 56 | 57 | // Matrices can be formatted, too, even using the same attributes as the contained values, 58 | // f.e. std::print("{:{}}", m, pad) for padding each individual value to some length. 59 | template 60 | struct std::formatter> : std::formatter { 61 | template 62 | auto format(const Matrix& m, FormatContext& ctx) const { 63 | for (int row = 0; row < m.rows(); ++row) { 64 | if (row > 0) { 65 | *ctx.out()++ = '\n'; 66 | } 67 | for (int col = 0; col < m.cols(); ++col) { 68 | if (col > 0) { 69 | *ctx.out()++ = ' '; 70 | } 71 | std::formatter::format(m[row, col], ctx); 72 | } 73 | } 74 | return ctx.out(); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /DynProgSolutions/MaximumSubarraySum.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n) = O(n), M(n) = O(1). The last 3 are used by maximumSubmatrixSum only 5 | void maximumSubarraySum(std::span values, int* result, int* from, int* to) { 6 | const int n = int(values.size()); 7 | // Negative values in the beginning of the array can be discarded 8 | int first = 0; 9 | while (first < n && values[first] < 0) { 10 | ++first; 11 | } 12 | int bestFrom = first, bestTo = first, currFrom = first; 13 | int current = values[first], best = values[first]; 14 | if (first == n) { 15 | // No positive numbers => maximum sum is 0, from the empty subarray 16 | if (result && from && to) { 17 | *result = *from = *to = 0; 18 | } 19 | } else for (int i = first + 1; i < n; i++) { 20 | current += values[i]; 21 | if (current < 0) { 22 | current = 0; 23 | currFrom = i + 1; 24 | } 25 | if (current > best) { 26 | best = current; 27 | bestFrom = currFrom; 28 | bestTo = i; 29 | } 30 | } 31 | 32 | if (result && from && to) { 33 | *result = best; 34 | *from = bestFrom; 35 | *to = bestTo; 36 | } else { 37 | std::println("The maximum subarray sum for the array {} is: {}", values, best); 38 | std::println("The subarray starts at index: {} and ends at index: {}", bestFrom, bestTo); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DynProgSolutions/MaximumSubmatrixSum.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | 3 | // T(n) = O(n^3), M(n) = O(n^2) 4 | void maximumSubmatrixSum(const Matrix& values) { 5 | const int n = values.rows(), m = values.cols(); 6 | Matrix prefixSums(n, m + 1); 7 | for (int i = 0; i < n; ++i) { 8 | prefixSums[i, 0] = 0; 9 | prefixSums[i, 1] = values[i, 0]; 10 | for (int j = 2; j <= m; ++j) { 11 | prefixSums[i, j] = prefixSums[i, j - 1] + values[i, j - 1]; 12 | } 13 | } 14 | 15 | std::vector temp(n); 16 | int best = values[0, 0]; // Note: assumes it's nonnegative 17 | int bestTop = 0, bestBottom = 0, bestLeft = 0, bestRight = 0; 18 | for (int left = 0; left < m; ++left) { 19 | for (int right = left; right < m; ++right) { 20 | for (int row = 0; row < n; row++) { 21 | temp[row] = prefixSums[row, right + 1] - prefixSums[row, left]; 22 | } 23 | int current; 24 | int currTop, currBottom; 25 | maximumSubarraySum(temp, ¤t, &currTop, &currBottom); 26 | if (current > best) { 27 | best = current; 28 | bestTop = currTop; 29 | bestBottom = currBottom; 30 | bestLeft = left; 31 | bestRight = right; 32 | } 33 | } 34 | } 35 | // return best; 36 | 37 | std::println("{}", values); 38 | std::println("The maximum submatrix sum is: {}", best); 39 | std::println("with top left corner at [{}, {}] and bottom right corner at [{}, {}].", 40 | bestTop, bestLeft, bestBottom, bestRight); 41 | } 42 | -------------------------------------------------------------------------------- /DynProgSolutions/MinimumCoinCount.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | #include 4 | 5 | // T(n,S) = O(nS), M(n,S) = O(S) 6 | void minimumCoinCount(std::span coins, const int S) { 7 | assert(S >= 0); 8 | constexpr int infinity = std::numeric_limits::max(); 9 | std::vector M(S + 1, infinity); 10 | M[0] = 0; 11 | for (int x = 1; x <= S; x++) { 12 | for (const int c : coins) { 13 | if (c <= x && M[x - c] != infinity) { 14 | M[x] = std::min(M[x], M[x - c] + 1); 15 | } 16 | } 17 | } 18 | // return M[S]; 19 | 20 | // (!) std::print("S = {} with coins: {}\n", S, coins); 21 | if (M[S] == infinity) { 22 | std::println("It's impossible to gather the selected sum with these coins!"); 23 | return; 24 | } else { 25 | std::print("Minimum coin count is {}:", M[S]); 26 | int sum = S; 27 | while (sum) { 28 | for (const int c : coins) 29 | if (M[sum] == M[sum - c] + 1) { 30 | std::print(" {}", c); 31 | sum -= c; 32 | break; 33 | } 34 | } 35 | std::println(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DynProgSolutions/MinimumCoinCountLimited.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include "Matrix.h" 3 | #include 4 | 5 | // T(n,S) = O(nS), M(n,S) = O(nS), can be reduced to M(n,S) = O(S) 6 | void minimumCoinCountLimited(std::span coins, const int S) { 7 | assert(S >= 0); 8 | const int n = int(coins.size()); 9 | constexpr int infinity = std::numeric_limits::max(); 10 | Matrix M(n + 1, S + 1, infinity); 11 | M[0, 0] = 0; 12 | for (int i = 1; i <= n; ++i) { 13 | for (int x = 1; x <= S; ++x) { 14 | if (coins[i - 1] <= x && M[i - 1, x - coins[i - 1]] != infinity) { 15 | M[i, x] = std::min(1 + M[i - 1, x - coins[i - 1]], M[i - 1, x]); 16 | } else { 17 | M[i, x] = M[i - 1, x]; 18 | } 19 | } 20 | } 21 | //return M[n, S]; 22 | 23 | // (!) std::print("S = {} with coins: {}\n", S, coins); 24 | if (M[n, S] == infinity) { 25 | std::println("It's impossible to gather the selected sum with these coins!"); 26 | } else { 27 | std::print("Minimum coin count is {}:", M[n, S]); 28 | for (int i = n, sum = S; sum != 0; --i) { 29 | for (const int c : coins) { 30 | if (c <= sum && M[i, sum] == M[i - 1, sum - c] + 1) { 31 | std::print(" {}", c); 32 | sum -= c; 33 | break; 34 | } 35 | } 36 | } 37 | std::println(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DynProgSolutions/MinimumDeletionsToPalindrome.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include // std::ranges::reverse, visualization only 3 | 4 | // T(n) = O(n^2); M(n) = O(n^2) -> can be optimized to O(n) if recovering the palindrome isn't required 5 | void minimumDeletionsToPalindrome(std::string_view str) { 6 | const int n = int(str.length()); 7 | // Invariant: M[i, j] contains the max length of a palindromic subsequence of str[i..j] 8 | Matrix M(n, n, 0); 9 | for (int i = 0; i < n; ++i) { 10 | M[i, i] = 1; 11 | } 12 | // When the "unused" values in the matrix are zeroes, this is technically not needed 13 | for (int i = 0; i < n - 1; ++i) { 14 | M[i, i + 1] = (str[i] == str[i + 1] ? 2 : 1); 15 | } 16 | for (int diag = 2; diag < n; ++diag) { 17 | for (int i = 0; i < n - diag; ++i) { 18 | const int j = i + diag; 19 | if (str[i] == str[j]) { 20 | M[i, j] = 2 + M[i + 1, j - 1]; // Always the best choice 21 | } else { 22 | M[i, j] = std::max(M[i + 1, j], M[i, j - 1]); // M[i+1, j-1] covered by these two options 23 | } 24 | } 25 | } 26 | // return n - M[0, n - 1]; 27 | // Recover the longest palindromic subsequence: 28 | std::string front, back; 29 | int i = 0, j = n - 1; 30 | while (i < j) { 31 | if (str[i] == str[j]) { 32 | front.push_back(str[i]); 33 | back.push_back(str[j]); 34 | ++i; --j; 35 | } else if (M[i, j] == M[i, j - 1]) { 36 | back.push_back(' '); 37 | --j; 38 | } else /*if (M[i, j] == M[i + 1, j])*/ { 39 | front.push_back(' '); 40 | ++i; 41 | } 42 | } 43 | if (i == j) { back.push_back(str[i]); } 44 | std::ranges::reverse(back); 45 | std::println("Longest palindromic subsequence\n for {}\n is: {}{}", str, front, back); 46 | std::println("Minimum deletions to palindrome: {}", n - M[0, n - 1]); 47 | } -------------------------------------------------------------------------------- /DynProgSolutions/MinimumSquareCount.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n) = O(n*sqrt(n)), M(n) = O(n) 5 | void minimumSquareCount(const int n) { 6 | const int infinity = std::numeric_limits::max(); 7 | std::vector M(n + 1, infinity); 8 | M[0] = 0; 9 | for (int k = 1; k <= n; k++) { 10 | for (int i = 1; i*i <= k; i++) { 11 | M[k] = std::min(M[k], M[k - i*i] + 1); 12 | } 13 | } 14 | // return M[n]; 15 | 16 | std::println("The minimum square count with sum {} is: {}", n, M[n]); 17 | int idx = n; 18 | while (idx > 0) { 19 | for (int i = 0; i*i <= idx; i++) { 20 | if (M[idx] == M[idx - i*i] + 1) { 21 | std::print("{}^2 ", i); 22 | idx -= i*i; 23 | } 24 | } 25 | } 26 | std::println(); 27 | } 28 | -------------------------------------------------------------------------------- /DynProgSolutions/NDigitIntegerCount.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n,S) = O(nS), M(n,S) = O(nS) <- can be reduced to M(n,S) = O(S) 5 | void nDigitIntegerCount(const int n, const int S) { 6 | assert(n > 0 && S >= 0); 7 | Matrix M(n, S + 1, 0); 8 | for (int i = 1; i < n; ++i) { 9 | M[i, 0] = 0; 10 | } 11 | for (int j = 0; j < 10; ++j) { 12 | M[0, j] = 1; 13 | } 14 | for (int j = 10; j <= S; ++j) { 15 | M[0, j] = 0; 16 | } 17 | for (int k = 1; k < n; k++) { 18 | for (int P = 1; P <= S; P++) { 19 | for (int i = 0; i < 10; i++) { 20 | M[k, P] += M.at(k - 1, P - i); 21 | } 22 | } 23 | } 24 | // M[n-1, S] if leading zeroes are allowed, M[n-1, S] - M[n-2, S] otherwise 25 | std::println("The number of {}-digit numbers with digit sum {} is {}.", n, S, M[n - 1, S] - M.at(n - 2, S)); 26 | } 27 | -------------------------------------------------------------------------------- /DynProgSolutions/OptimalSingleRobotPath.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n,m) = O(nm), M(n,m) = O(nm) <- can be reduced to M(n,m) = O(min{n,m}) 5 | void optimalSingleRobotPath(const Matrix& field) { 6 | const int n = field.rows(), m = field.cols(); 7 | const int minusInf = std::numeric_limits::min(); 8 | Matrix M(n, m, minusInf); 9 | M[n - 1, m - 1] = field[n - 1, m - 1]; 10 | for (int i = n - 2; i >= 0; --i) { 11 | M[i, m - 1] = M[i + 1, m - 1] + field[i, m - 1]; 12 | } 13 | for (int j = m - 2; j >= 0; --j) { 14 | M[n - 1, j] = M[n - 1, j + 1] + field[n - 1, j]; 15 | } 16 | for (int i = n - 2; i >= 0; --i) { 17 | for (int j = m - 2; j >= 0; --j) { 18 | M[i, j] = field[i, j] + std::max(M[i + 1, j], M[i, j + 1]); 19 | } 20 | } 21 | //return M[0, 0]; 22 | 23 | std::println("{}", field); 24 | std::print("Maximum profit path: "); 25 | int i = 0, j = 0; 26 | while (true) { 27 | if (i + 1 < n && M[i, j] == M[i + 1, j] + field[i, j]) { 28 | std::print("D"); ++i; 29 | } else if (j + 1 < m && M[i, j] == M[i, j + 1] + field[i, j]) { 30 | std::print("R"); ++j; 31 | } else { 32 | break; // we have reached the bottom 33 | } 34 | } 35 | std::println("\nMaximum profit: {}", M[0, 0]); 36 | } 37 | -------------------------------------------------------------------------------- /DynProgSolutions/OptimalTwoRobotPath.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | // Debugging & visualization only 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // T(n) = O(n^3), M(n) = O(n^3), where n^2 is the input size (!) 10 | // Again, can be reduced to M(n) = O(n^2), but without reconstructing the paths 11 | void optimalTwoRobotPath(const Matrix& field) { 12 | const int n = field.rows(), m = field.cols(); 13 | constexpr int minusInf = std::numeric_limits::min(); 14 | // three dimensions, corresponding to x1,x2 & y2 (y1=x2+y2-x1) 15 | std::vector arr((n + 1) * (n + 1) * (m + 1), minusInf); 16 | auto M = std::mdspan{arr.data(), n + 1, n + 1, m + 1}; 17 | for (int x1 = n - 1; x1 >= 0; --x1) { 18 | for (int x2 = n - 1; x2 >= 0; --x2) { 19 | for (int y2 = m - 1; y2 >= 0; --y2) { 20 | const int y1 = x2 + y2 - x1; 21 | if (y1 < 0 || y1 >= m) { // Not all pairs of positions are possible (!) 22 | continue; 23 | } 24 | int m = std::max({ M[x1, x2, y2 + 1], 25 | M[x1, x2 + 1, y2], 26 | M[x1 + 1, x2, y2 + 1], 27 | M[x1 + 1, x2 + 1, y2] }); 28 | if (m == minusInf) { 29 | m = 0; 30 | } 31 | M[x1, x2, y2] = field[x1, y1] + ((x1 == x2) ? 0 : field[x2, y2]) + m; 32 | } 33 | } 34 | } 35 | // return M[0, 0, 0]; 36 | // Reconstruct both robots' paths: 37 | std::string r1, r2; 38 | r1.reserve(n + m - 2); 39 | r2.reserve(n + m - 2); 40 | int x1 = 0, y1 = 0, x2 = 0, y2 = 0; 41 | while (x1 < n && x2 < n && y1 < m && y2 < m) { 42 | const int tmp = field[x1, y1] + ((x1 == x2) ? 0 : field[x2, y2]); 43 | if (y1 < m - 1 && y2 < m - 1 && M[x1, x2, y2] == tmp + M[x1, x2, y2 + 1]) { 44 | ++y1; ++y2; r1 += 'R'; r2 += 'R'; 45 | } else if (y1 < m - 1 && x2 < n - 1 && M[x1, x2, y2] == tmp + M[x1, x2 + 1, y2]) { 46 | ++y1; ++x2; r1 += 'R'; r2 += 'D'; 47 | } else if (x1 < n - 1 && y2 < m - 1 && M[x1, x2, y2] == tmp + M[x1 + 1, x2, y2 + 1]) { 48 | ++x1; ++y2; r1 += 'D'; r2 += 'R'; 49 | } else if (x1 < n - 1 && x2 < n - 1 && M[x1, x2, y2] == tmp + M[x1 + 1, x2 + 1, y2]) { 50 | ++x1; ++x2; r1 += 'D'; r2 += 'D'; 51 | } else { 52 | break; 53 | } 54 | } 55 | // Pretty-print the matrix better by padding each value 56 | const size_t pad = std::ranges::max(field | std::views::transform([](const int x) { return std::formatted_size("{}", x); })); 57 | std::println("{:{}}", field, pad); 58 | std::println("Maximum profit paths:\n{}\n{}", r1, r2); 59 | std::println("Maximum profit for two robots: {}.", M[0, 0, 0]); 60 | } -------------------------------------------------------------------------------- /DynProgSolutions/Partition.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n) = O(nS), M(n) = O(nS) <- can be reduced to M(n) = O(S) 5 | void Partition(std::span values) { 6 | int S = 0; 7 | for (auto v : values) { 8 | S += v; 9 | } 10 | assert(S % 2 == 0); 11 | S /= 2; 12 | const int n = int(values.size()); 13 | Matrix M(n, S + 1, 0); 14 | for (int P = 1; P <= S; P++) { 15 | M[0, P] = (P == values[0]); 16 | } 17 | for (int i = 1; i < n; i++) { 18 | for (int P = 0; P <= S; P++) { 19 | if (values[i] > P) { 20 | M[i, P] = M[i - 1, P]; 21 | } else { 22 | M[i, P] = M[i - 1, P] || M[i - 1, P - values[i]]; 23 | } 24 | } 25 | } 26 | // return M[n - 1, S]; 27 | 28 | if (!M[n - 1, S]) { 29 | std::println("There is no possible partition for the multiset {}", values); 30 | } else { 31 | std::print("One possible partition for the multiset {}\nis:", values); 32 | int P = S; 33 | for (int k = n - 1; k >= 0; --k) { 34 | if ((k == 0 && M[0, P]) || (P >= values[k] && M[k - 1, P - values[k]])) { 35 | std::print(" {}", values[k]); 36 | P -= values[k]; 37 | } 38 | } 39 | std::println(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DynProgSolutions/PictureProfit.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n) = O(n), M(n) = O(n) 5 | void pictureProfit(std::span values) { 6 | const int n = int(values.size()); 7 | std::vector M(n + 1, 0); 8 | M[1] = values[0]; 9 | for (int k = 2; k <= n; k++) { 10 | M[k] = std::max(values[k - 1] + M[k - 2], M[k - 1]); 11 | } 12 | // return M[n]; 13 | 14 | std::print("The optimal profit is: {}.\n", M[n]); 15 | std::print("The following pictures are being taken:\n"); 16 | int idx = n; 17 | while (idx > 0) { 18 | if (idx == 1 /*?*/|| M[idx] == values[idx - 1] + M[idx - 2]) { 19 | std::print("[{}]: {}\n", idx, values[idx - 1]); 20 | idx -= 2; 21 | } else { 22 | --idx; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DynProgSolutions/SingleRobotPathCount.cpp: -------------------------------------------------------------------------------- 1 | #include "DynProgProblems.h" 2 | #include 3 | 4 | // T(n,m) = O(nm), M(n,m) = O(nm) <- can be reduced to M(n,m) = O(min{n,m}) 5 | void singleRobotPathCount(const Matrix& blocked) { 6 | const int n = blocked.rows(), m = blocked.cols(); 7 | Matrix M(n, m, 0); 8 | M[n - 1, m - 1] = blocked[n - 1, m - 1] ? 0 : 1; 9 | for (int i = n - 2; i >= 0; --i) { 10 | M[i, m - 1] = (!blocked[i, m - 1] && !blocked[i + 1, m - 1]) ? M[i + 1, m - 1] : 0; 11 | } 12 | for (int j = m - 2; j >= 0; --j) { 13 | M[n - 1, j] = (!blocked[n - 1, j] && !blocked[n - 1, j + 1]) ? M[n - 1, j + 1] : 0; 14 | } 15 | for (int i = n - 2; i >= 0; --i) { 16 | for (int j = m - 2; j >= 0; --j) { 17 | if (!blocked[i, j] && !blocked[i + 1, j]) { 18 | M[i, j] += M[i + 1, j]; 19 | } 20 | if (!blocked[i, j] && !blocked[i, j + 1]) { 21 | M[i, j] += M[i, j + 1]; 22 | } 23 | } 24 | } 25 | //return M[0, 0]; 26 | std::println("{}", blocked); 27 | std::println("The number of paths is {}.", M[0, 0]); 28 | } 29 | -------------------------------------------------------------------------------- /DynamicProgrammingProblems.md: -------------------------------------------------------------------------------- 1 | ## Задачи за Динамично програмиране 2 | 3 | 1. **[MinimumCoinCount]** Разполагаме с **n** вида монети със стойности **c1**,**c2**,...,**cn** и неограничен брой монети от всеки вид. С колко най-малко монети може да се събере сума **S**? 4 | 2. **[MinimumCoinCountLimited]** Нека е дадено конкретно мултимножество монети със стойности **c1**,**c2**,...,**cn**. С колко най-малко монети може да се събере сума **S**? 5 | 3. **[Tiling2xN]** По колко начина може да се покрие дъска с размери **2xN** с плочки **2x1**? Плочките могат да се слагат хоризонтално и вертикално и не трябва да "излизат" от границите на дъската. 6 | 4. **[PictureProfit]** В галерия са подредени в редица **n** картини на стойност **c1**,**c2**,...,**cn**. Можем да откраднем колкото картини искаме, стига да не взимаме някоя двойка съседни. Каква най-голяма печалба можем да получим? 7 | 5. **[PictureProfitC]** Ами ако картините от горната задача са подредени в кръг и картини **1** и **n** също са съседни помежду си? 8 | 6. **[MinimumSquareCount]** Да се намери минималният брой квадрати на естествени числа (може да се повтарят), които дават сума **N**. 9 | 7. **[CopyPaste]** Нека сме отворили празен текстови файл и имаме 4 варианта за действие - да вмъкнем един символ накрая (insert), да селектираме цялото текущо съдържание на файла (Ctrl-A), да копираме селектираното в буфера (Ctrl-C, има смисъл само след Ctrl-A) и да поставим цялото съдържание на буфера в края на файла (Ctrl-V, отново има смисъл само след Ctrl-C и оставя буфера празен). Да се изчисли колко най-голям може да стане файлът след **n** действия. Ами ако Ctrl-V не оставя буфера празен и е позволено последователното поставяне? 10 | 8. **[FunnyIterator]** Нека е дадено естествено число **n**. Можем или да го намалим с едно, или да го разделим на две (но само ако е четно) или да го разделим на три (само ако се дели точно). Да се намери с колко най-малко действия можем от числото **n** да получим единица. 11 | 9. **[LongestIncreasingSubsequence]** Да се намери най-голямата нарастваща подредица в масив от числа. 12 | 10. **[MatrixMultiplication]** Дадени са **n** матрици с размери **d0\*d1**, **d1\*d2**,...,**dn-1\*dn**. Знаем, че "цената" да умножим матрица **m\*n** с матрица **n\*p** е **mnp**. Да се намери в какъв ред трябва да бъдат умножени всички матрици, за да се постигне минимална сумарна цена. 13 | 11. **[OptimalSingleRobotPath]** Робот се намира в горния ляв край на матрица **NxM** и може да се движи само надолу и надясно. Каква е максималната сума от числа, която може да се събере по някой маршрут до долния десен ъгъл на матрицата? 14 | 12. **[OptimalTwoRobotPath]** Два робота тръгват едновременно от горния ляв ъгъл на матрица **NxM**, движейки се само надолу и надясно. Каква е максималната сума, която могат общо да съберат, стигайки независимо до долния десен ъгъл? Ако и двата минат през една и съща позиция, числото ще се прибави само веднъж към сумата. 15 | 13. **[BinomialCoefficient]** Да се изчисли биномния коефициент **n** над **k**. 16 | 14. **[nDigitIntegerCount]** Колко са **n**-цифрените числа в десетична бройна система със сума на цифрите **S**? 17 | 15. **[Knapsack]** 18 | 16. **[KnapsackNoRepetitions]** 19 | 17. **[Partition]** 20 | 18. **[EqualSumSubsets]** 21 | 19. **[MaximumSubarraySum]** Да се намери максималната сума, образувана от някой подмасив на даден масив с числа. Забележете разликата подмасив != подредица. 22 | 20. **[MaximumSubmatrixSum]** Да се намери максималната сума, образувана от някоя подматрица на дадена матрица с числа. 23 | 21. **[LongestCommonSubsequence]** Да се намери най-дългата обща подредица на два масива 24 | 22. **[IntegerPartitions]** По колко начина може число **N** да се разбие на сума от по-малки числа? Например за N=4 има 5 начина: 4, 3+1, 2+2, 2+1+1 и 1+1+1+1 - забележете, че не разпознаваме 1+3 или 1+2+1 като възможности. 25 | 23. **[LevenshteinDistance]** Нека са дадени два низа, **s1** и **s2**, и са позволени следните операции за редактиране: вмъкване на символ на произволна позиция, премахване на символ от произволна позиция, и субституция на символ на произволна позиция с друг. Да се намери минималния брой операции, нужни за получаването на низа **s2** чрез редактиране на **s1**. 26 | 24. **[MinimumPalindromeSubstrings]** Да се намери минималният брой палиндроми, на които може да се разбие даден низ. 27 | 25. **[MinimumDeletionsToPalindrome]** Да се намери колко най-малко символа могат да се премахнат от даден низ до получаването на палиндром. 28 | 26. **[LargestWhiteSquare]** Даден е черно-бял bitmap. Да се намери най-големият бял квадрат. 29 | -------------------------------------------------------------------------------- /Graph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include // std::unique_ptr 3 | #include // size_t 4 | #include // graph input 5 | #include // std::is_void_v, std::conditional_t 6 | // The libraries below are used only for random graph generation 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include // std::swap. std::exchange 12 | 13 | #include 14 | #define vassert assert 15 | 16 | // As standard, vertices will be labeled with consecutive nonnegative numbers 17 | using Vertex = size_t; 18 | // A weighted edge will be represented by a simple 19 | // tuple-like struct, and will be ordered by weight. 20 | template 21 | struct Edge { Vertex u, v; W w; }; 22 | template 23 | bool operator<(const Edge& lhs, const Edge& rhs) { return (lhs.w < rhs.w); } 24 | 25 | // An implementation class handles directionality. Not meant to be used directly (!) 26 | namespace impl { template class Graph; } 27 | 28 | // The user-facing types are "Graph" (for unweighted graphs) and "WeightedGraph" 29 | using Graph = impl::Graph; 30 | template 31 | using WeightedGraph = impl::Graph; 32 | 33 | // Generate an undirected, unweighted graph with n vertices and m edges. 34 | // Warning: uses hard O(n^2) time & space due to explicit generation of all possible edges. 35 | inline Graph makeRandomUnweighted(const Vertex n, const Vertex m); 36 | // Generate an undirected, weighted graph with n vertices and 37 | // m edges with random weights of type W between wmin and wmax. 38 | // Warning: uses hard O(n^2) time & space due to explicit generation of all possible edges. 39 | template 40 | WeightedGraph makeRandomWeighted(const Vertex n, const Vertex m, const W wmin, const W wmax); 41 | 42 | namespace impl { 43 | // A vertex-weight pair. Used both by the Graph internally, as well as 44 | // for "predecessor-distance" pair in Dijkstra & Prim algorithms. 45 | template 46 | struct AdjPair { Vertex v; W w; }; 47 | template 48 | bool operator<(const AdjPair& lhs, const AdjPair& rhs) { return (lhs.w < rhs.w); } 49 | 50 | template 51 | class Graph { 52 | public: 53 | static const bool isWeighted = !std::is_void_v; 54 | using AdjPair = std::conditional_t, Vertex>; 55 | using iterator = const AdjPair*; 56 | private: 57 | std::unique_ptr neighbours; 58 | std::unique_ptr< size_t[]> offsets; 59 | size_t n, m; 60 | public: 61 | explicit Graph(std::istream& is) { 62 | is >> n; 63 | is >> m; 64 | offsets = std::make_unique< size_t[]>(n + 1); 65 | neighbours = std::make_unique(2 * m); // Each edge is present in the adjacency lists of both of it's vertices 66 | size_t currOff = 0; 67 | for (size_t i = 0; i < n; ++i) { 68 | offsets[i] = currOff; 69 | size_t numNeighbs = 0; 70 | is >> numNeighbs; 71 | for (size_t j = 0; j < numNeighbs; ++j) { 72 | Vertex v = 0; // Used for debugging purposes only 73 | if constexpr (isWeighted) { 74 | is >> neighbours[currOff].v; 75 | is >> neighbours[currOff].w; 76 | v = neighbours[currOff].v; 77 | } else { 78 | is >> neighbours[currOff]; 79 | v = neighbours[currOff]; 80 | } 81 | vassert(v < n && "Invalid vertex index!"); 82 | vassert(v != i && "A vertex cannot be its own neighbour"); 83 | ++currOff; 84 | } 85 | } 86 | vassert(currOff == 2 * m); 87 | offsets[n] = currOff; 88 | } 89 | iterator vBegin(const Vertex v) const { 90 | vassert(v < numVertices()); 91 | return (&neighbours[offsets[v]]); 92 | } 93 | iterator vEnd(const Vertex v) const { 94 | vassert(v < numVertices()); 95 | return (&neighbours[offsets[v + 1]]); 96 | } 97 | size_t numVertices() const { 98 | return n; 99 | } 100 | size_t numEdges() const { 101 | return m; 102 | } 103 | }; 104 | 105 | template 106 | Graph makeRandom(const size_t n, const size_t m, Distr& distr) { 107 | const size_t nchk2 = n * (n - 1) / 2; // Maximum number of edges 108 | vassert(m <= nchk2); 109 | static std::mt19937_64 eng{ uint64_t(std::chrono::steady_clock::now().time_since_epoch().count()) }; 110 | // Create an array, containing all possible edges 111 | using AdjPair = typename impl::Graph::AdjPair; 112 | std::vector> allEdges(nchk2); 113 | size_t idx = 0; 114 | for (Vertex i = 0; i < n; ++i) 115 | for (Vertex j = i + 1; j < n; ++j) 116 | if constexpr (impl::Graph::isWeighted) { 117 | allEdges[idx++] = { i,{j,distr(eng)} }; 118 | } else { 119 | allEdges[idx++] = { i,j }; 120 | } 121 | // Use Fisher-Yates shuffle to generate a makeRandom subset of m edges. 122 | // (select either m or nchk2-m edges, depending on which is smaller) 123 | const size_t edgesToChoose = ((m < nchk2 / 2) ? m : nchk2 - m); 124 | for (size_t i = 0; i < edgesToChoose; ++i) { 125 | const auto choice = eng() % (nchk2 - i); 126 | std::swap(allEdges[i], allEdges[i + choice]); 127 | } 128 | // Locate the selected edges as a subrange of all edges 129 | const auto from = ((m < nchk2 / 2) ? allEdges.cbegin() : allEdges.cbegin() + nchk2 - m); 130 | const auto to = ((m < nchk2 / 2) ? allEdges.cbegin() + m : allEdges.cend()); 131 | // Separate each vertex's adjacency list 132 | std::vector> adjList(n); 133 | for (auto it = from; it != to; ++it) { 134 | auto& [u, p] = *it; 135 | if constexpr (impl::Graph::isWeighted) { 136 | auto& [v, w] = p; 137 | adjList[u].push_back({ v, w }); 138 | adjList[v].push_back({ u, w }); 139 | } else { 140 | auto v = p; 141 | adjList[u].push_back(v); 142 | adjList[v].push_back(u); 143 | } 144 | } 145 | // We don't need the edges anymore, and clear() may not deallocate the memory 146 | std::exchange(allEdges, {}); 147 | // Serialize the adjacency list into the format, expected by impl::Graph constructor 148 | std::stringstream result; 149 | result << n << ' ' << m << ' '; 150 | for (auto& lst : adjList) { 151 | result << lst.size() << ' '; 152 | for (const AdjPair& p : lst) { 153 | if constexpr (impl::Graph::isWeighted) { 154 | result << p.v << ' ' << p.w << ' '; 155 | } else { 156 | result << p << ' '; 157 | } 158 | } 159 | // Clear up, one adjacency list at a time 160 | std::exchange(lst, {}); 161 | } 162 | return Graph{ result }; 163 | } 164 | } // namespace impl 165 | 166 | // We can now delegate generating of weighted & unweighted 167 | // graphs to the single function in namespace impl. 168 | Graph makeRandomUnweighted(const size_t n, const size_t m) { 169 | using Distr = std::uniform_int_distribution; 170 | Distr dummy{}; // Dummy distribution, will not be used. 171 | return impl::makeRandom(n, m, dummy); 172 | } 173 | template 174 | WeightedGraph makeRandomWeighted(const size_t n, const size_t m, const W wmin, const W wmax) { 175 | static_assert(std::is_integral_v || std::is_floating_point_v, 176 | "Only numeric weights are supported for random graph generation!"); 177 | using Distr = std::conditional_t, std::uniform_int_distribution, std::uniform_real_distribution>; 178 | Distr distr{ wmin,wmax }; 179 | return impl::makeRandom(n, m, distr); 180 | } 181 | -------------------------------------------------------------------------------- /GraphAlgorithm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Graph.h" 3 | #include "UnionFind.h" 4 | #include "binary_heap_static.h" // from Andreshk/AdvancedDataStructures repo 5 | #include "pairing_heap.h" // from Andreshk/AdvancedDataStructures repo 6 | #include "pairing_heap_static.h" // from Andreshk/AdvancedDataStructures repo 7 | #include 8 | #include // std::sort 9 | 10 | // Example usage: 11 | // const auto g = makeRandomWeighted(8, 15, 1, 5); 12 | // const auto mstEdges = Kruskal(g); 13 | // int totalWeight = 0; 14 | // std::cout << g << "MST edges:"; 15 | // for (const auto& e : mstEdges) { 16 | // std::cout << " (" << e.u << ',' << e.v << "){" << e.w << '}'; 17 | // totalWeight += e.w; 18 | // } 19 | // std::cout << "\nMST weight = " << totalWeight << "\n\n"; 20 | template 21 | std::vector> Kruskal(const WeightedGraph& graph) { 22 | // First, build the list of all edges 23 | std::vector> edges(graph.numEdges()); 24 | size_t idx = 0; 25 | for (Vertex u = 0; u < graph.numVertices(); ++u) { 26 | const auto from = graph.vBegin(u); 27 | const auto to = graph.vEnd(u); 28 | for (auto it = from; it != to; ++it) { 29 | const auto& [v, w] = *it; 30 | if (u < v) { // Do not count the edges twice (!) 31 | edges[idx++] = { u,v,w }; 32 | } 33 | } 34 | } 35 | // Sort the edges by weight 36 | std::sort(edges.begin(), edges.end()); 37 | const size_t n = graph.numVertices(); 38 | // Initialize the disjoint set with n elements, each in its own component 39 | UnionFind uf{ n }; 40 | std::vector> treeEdges; 41 | treeEdges.reserve(n - 1); 42 | // Until n-1 edges have been added (or the list is exhausted) 43 | for (const auto& e : edges) { 44 | if (treeEdges.size() == n - 1) 45 | break; 46 | // Union uses Find internally to determine whether the two vertices are already 47 | // in the same component, and returns true on successful union (false otherwise). 48 | if (uf.Union(e.u, e.v)) { 49 | treeEdges.push_back(e); 50 | } 51 | } 52 | return treeEdges; 53 | } 54 | 55 | // Example usage: 56 | // const auto g = makeRandomWeighted(8, 15, 1, 5); 57 | // const auto dists = Dijkstra(g, 0); 58 | // std::cout << "Distances from vertex 0:"; 59 | // for (size_t i = 0; i < dists.size(); ++i) { 60 | // std::cout << ' ' << i << "<-" << dists[i].first << '{' << dists[i].second << '}'; 61 | // } 62 | // std::cout << "\n\n"; 63 | template 64 | std::vector::AdjPair> Dijkstra(const WeightedGraph& graph, const Vertex u) { 65 | using AdjPair = typename WeightedGraph::AdjPair; 66 | const size_t n = graph.numVertices(); 67 | const W infinity = std::numeric_limits::max(); 68 | // The result: for each vertex i, result[i].first is the predecessor of i, 69 | // and result[i].second is the minimum distance from the starting vertex to i. 70 | // Of course, if the predecessors aren't needed, this can be simplified to vector. 71 | std::vector result(n); 72 | for (Vertex i = 0; i < n; ++i) { 73 | result[i] = { i, infinity }; 74 | } 75 | // The data structure is at the core of the algorithm 76 | PairingHeap ph{}; 77 | // These proxies allow us to access the values, stored in the heap for each vertex. 78 | std::vector proxies(n); 79 | std::vector visited(n, false); 80 | // In the beginning, all vertices are at distance infinity. 81 | proxies[u] = ph.insert({ u,0 }); 82 | while (!ph.empty()) { 83 | const auto [v, dist] = ph.extractMin(); 84 | // Once a vertex is extracted, we know its final distance from the start. 85 | result[v].w = dist; 86 | visited[v] = true; 87 | proxies[v] = decltype(ph)::proxy{}; // the proxy is invalid anyways 88 | const auto from = graph.vBegin(v); 89 | const auto to = graph.vEnd(v); 90 | // Try inserting/relaxing each unvisited neighbour of the current vertex. 91 | for (auto it = from; it != to; ++it) { 92 | const auto& [v1, w] = *it; 93 | if (!visited[v1]) { 94 | const AdjPair newEdge{ v1,dist + w }; 95 | if (!proxies[v1]) { // Insert a vertex into the heap 96 | proxies[v1] = ph.insert(newEdge); 97 | result[v1].v = v; 98 | } else if (ph.decreaseKey(proxies[v1], newEdge)) { 99 | // If decreaseKey() was successful, then v1 should have a new predecessor. 100 | result[v1].v = v; 101 | } 102 | } 103 | } 104 | } 105 | return result; 106 | } 107 | 108 | // Dijkstra's algorithm, using the specially adapted "static" heaps: 109 | // PairingHeapStatic and BinaryHeapStatic. Example usage: 110 | // const auto g = makeRandomWeighted(8, 15, 1, 5); 111 | // const auto dists1 = DijkstraS(g, 0); 112 | // const auto dists2 = DijkstraS(g, 0); 113 | template class StaticHeap, typename W> 114 | std::vector::AdjPair> DijkstraS(const WeightedGraph& graph, const Vertex u) { 115 | using AdjPair = typename WeightedGraph::AdjPair; 116 | const size_t n = graph.numVertices(); 117 | const W infinity = std::numeric_limits::max(); 118 | // The result: for each vertex i, result[i].first is the predecessor of i, 119 | // and result[i].second is the minimum distance from the starting vertex to i. 120 | // Of course, if the predecessors aren't needed, this can be simplified to vector. 121 | std::vector result(n); 122 | for (Vertex i = 0; i < n; ++i) { 123 | result[i] = { i, infinity }; 124 | } 125 | // The data structure is at the core of the algorithm 126 | StaticHeap ph{ n,u,W{ 0 },infinity }; 127 | while (!ph.empty()) { 128 | const auto [v, dist] = ph.extractMin(); 129 | if (dist == infinity) { 130 | break; // There are unreachable vertices from the start, so nothing more to do. 131 | } 132 | // Once a vertex is extracted, we know its final distance from the start. 133 | result[v].w = dist; 134 | const auto from = graph.vBegin(v); 135 | const auto to = graph.vEnd(v); 136 | // Try relaxing each unvisited neighbour of the current vertex. 137 | for (auto it = from; it != to; ++it) { 138 | const auto& [v1, w] = *it; 139 | if (ph.contains(v1) && ph.decreaseKey(v1, dist + w)) { 140 | // If decreaseKey() was successful, then v1 should have a new predecessor. 141 | result[v1].v = v; 142 | } 143 | } 144 | } 145 | return result; 146 | } 147 | 148 | // Example usage: same as Kruskal's algorithm 149 | template 150 | std::vector> Prim(const WeightedGraph& graph) { 151 | using AdjPair = typename WeightedGraph::AdjPair; 152 | const size_t n = graph.numVertices(); 153 | const W infinity = std::numeric_limits::max(); 154 | // For each vertex i, preds[i].v is the vertex, whose edge to i is included 155 | // in the MST when adding i, and preds[i].w is this edge's weight. 156 | std::vector preds(n); 157 | for (Vertex i = 0; i < n; ++i) { 158 | preds[i] = { i, infinity }; 159 | } 160 | // The data structure is at the core of the algorithm 161 | PairingHeap ph{}; 162 | // These proxies allow us to access the values, stored in the heap for each vertex. 163 | std::vector proxies(n); 164 | std::vector visited(n, false); 165 | // The resulting array of the MST's edges. 166 | std::vector> treeEdges; 167 | treeEdges.reserve(n - 1); 168 | // This algorithm assumes that the graph is connected, and the MST can be 169 | // built, staring from any vertex - so we choose to start from vertex 0. 170 | // Otherwise, place the rest of this function in a loop over the unvisited 171 | // for (Vertex start = 0; start < n; ++start) if (!visited[start]) { ... } 172 | // - this will make an MST for each of the connected components. 173 | const Vertex start = 0; 174 | proxies[start] = ph.insert({ start,0 }); 175 | while (!ph.empty()) { 176 | const auto [v, dist] = ph.extractMin(); 177 | //preds[v].w = dist; 178 | // Once a vertex is extracted, it is considered added to the MST. 179 | if (v != start) { // No need to "add" the starting vertex 0 180 | treeEdges.push_back({ preds[v].v, v, dist }); 181 | } 182 | visited[v] = true; 183 | proxies[v] = decltype(ph)::proxy{}; // the proxy is invalid anyways 184 | const auto from = graph.vBegin(v); 185 | const auto to = graph.vEnd(v); 186 | // Try inserting/relaxing each unvisited neighbour of the current vertex. 187 | for (auto it = from; it != to; ++it) { 188 | const auto& [v1, w] = *it; 189 | if (!visited[v1]) { 190 | const AdjPair newEdge{ v1, w }; // Here is the ONLY difference with Dijkstra's algorithm (!) 191 | if (!proxies[v1]) { // Insert a vertex into the heap 192 | proxies[v1] = ph.insert(newEdge); 193 | preds[v1].v = v; 194 | } else if (ph.decreaseKey(proxies[v1], newEdge)) { 195 | // If decreaseKey() was successful, then v1 should have a new predecessor. 196 | preds[v1].v = v; 197 | } 198 | } 199 | } 200 | } 201 | return treeEdges; 202 | } 203 | -------------------------------------------------------------------------------- /LinearStringSorting.md: -------------------------------------------------------------------------------- 1 | ## Сортиране на списък от стрингове в _линейно_ време и с линейна допълн. памет 2 | 3 | Нека имаме `n` на брой думи с обща дължина `m`. Под _линейно_ време ще разбираме `O(m)`. Това е общата големина на входа, за която обаче няма строго съответствие с броя елементи - разлика с масивите с числа, например - заради което двете големини се разглеждат поотделно. 4 | 5 | Нека създадем празен [Trie](https://en.wikipedia.org/wiki/Trie) (чете се "_трай_") и вмъкнем всяка дума от списъка в него. Заради особеността на тази структура вмъкването на дума в нея отнема време и памет, линейни по големината на тази дума, без значение от броя вече съдържани други думи (!) => вмъкването на всички ще отнеме общо `O(m)` време и допълнителна памет. Б.о.о. можем да допуснем, че възлите в това дърво пазят наследниците си подредени - например с масив от указатели, в който първият съответства на буквичката `'a'`, вторият на `'b'` и т.н. Аналогът на обхождане корен-ляво-дясно в такова дърво първо би посетило наследника с `'a'` (ако съществува), после с `'b'` и т.н. преди да се върне нагоре по рекурсивните извиквания. Това обхождане би отнело също `O(m)` време и памет и с него ще получим думите от първоначалния списък в лексикографска наредба, за общо време и допълнителна памет `O(m)` - линейни по големината на входа. 6 | 7 | Как се използва за числа - можем да си представяме неотрицателните* числа като "думи" от битове, всяка с дължина `lgn` бита. Тогава `m = nlgn` и получаваме отново `O(nlgn)` алгоритъм за сортиране на числа, въпреки че не използва директни сравнения. 8 | 9 | _Заб.:_ алгоритъмът има голяма теоретична полза, както и връзка с много други алгоритми и структури от данни за стрингове. На практика обаче е неефективен заради огромното количество допълнителна памет (възлите в дървото са `O(n)`, но заемат по много памет), както и относително бавното траверсиране на дървото (hint: cache/memory locality) 10 | 11 | *Ако имаме отрицателни числа, то можем предварително да "изместим" всяко число в списъка така, че да станат неотрицателни, и да ги възстановим след сортирането. 12 | -------------------------------------------------------------------------------- /LowerBounds.md: -------------------------------------------------------------------------------- 1 | П1∝П2 (за символа потърсете "\propto" в Google Images); 2 | "Трудна" задача ∝ Неизвестна задача 3 | 4 | "Ако П2 се решаваше бързо, то и П1 щеше да се решава бързо" 5 | 6 | Обратното, ако П1 се решава "бавно", то и П2 трябва да се решава "бавно" 7 | 8 | - известно "трудни" (бавни) = Ω(nlgn): 9 | - Сортиране на масив 10 | - Element Uniqueness (EU) 11 | - неизвестни (засега): 12 | - Mode 13 | - Minimal Distance (MinDist) 14 | - Closest Pair (Minimal Distance in 2D) 15 | - Minimal Distance in 3D (MinDist3D) 16 | 17 | 18 | ### Доказателство за долна граница на Mode 19 | #### Редукция Element Uniqueness ∝ Mode 20 | ``` 21 | EU ∝ Mode 22 | EU1(A[1..n] : array of integers) 23 | 1. m <- Mode(A[1..n]) 24 | 2. if Count(m,A[1..n]) = 1 25 | 3. return Yes 26 | 4. else 27 | 5. return No 28 | ``` 29 | Доказателство за коректност на редукцията: Нека `m` е резултатът, който връща алгоритъмът `Mode`, т.е. някой най-често срещан елемент в масива (може да не е уникален). Тъй като `Mode` работи коректно, то не съществува елемент в масива, който се среща в него повече пъти (иначе - противоречие с коректността му). Ако броя срещания на този най-често срещан елемент в масива е 1, то не съществува елемент, който да се среща повече пъти => всички елементи в масива са уникални. Тогава отговорът на задачата Element Uniqueness трябва да бъде "Да", и точно това връща алгоритъмът в този случай. Ако този най-често срещан елемент се среща повече от веднъж в масива, то не всички елементи в него са уникални и алгоритъмът ни трябва да върне "Не" - което той прави. 30 | 31 | Ако времето за изпълнение на `Mode` е o(nlgn), то времето за изпълнение на `EU` щеше да е o(nlgn) + O(n), което пак е o(nlgn). Но е доказано, че Element Uniqueness не може да бъде решена във време o(nlgn) => противоречие => не е възможно и Mode да бъде решавана в o(nlgn) време. 32 | 33 | ### Доказателство за долна граница на Minimal Distance 34 | #### Редукция Element Uniqueness ∝ Minimal Distance 35 | ``` 36 | EU ∝ Minimal Distance 37 | EU2(A[1..n] : array of integers) 38 | 1. dist <- MinDist(A[1..n]) 39 | 2. if dist = 0 40 | 3. return No 41 | 4. else 42 | 5. return Yes 43 | ``` 44 | Доказателство за коректност на редукцията: Нека `d` е резултатът, който `MinDist` връща, т.е. най-малката разлика между две числа на различни позиции в масива. Ако това минимално разстояние е 0, то в масива съществуват два елемента на различни позиции, които са равни => не всички елементи в масива са уникални. В такъв случай алгоритъмът `EU2` връща "Не", което е коректния отговор. Ако минималното разстояние е >0, то не съществуват различни елементи в масива, които да са равни помежду си (в противен случай `MinDist` щеше да върне 0). Тогава под дефиниция всички елементи в масива са уникални и алгоритъмът връща "Да", което отново е коректния отговор. 45 | 46 | Ако задачата Minimal Distance се решаваше за време o(nlgn), то и `EU2` щеше да работи за o(nlgn) време => противоречие с доказаното на лекции, че Element Uniqueness винаги се решава за Ω(nlgn) време => Minimal Distance също винаги се решава за Ω(nlgn). 47 | 48 | 49 | ### Доказателство за долна граница на Minimal Distance in 3D 50 | #### Редукция MinDist ∝ MinDist3D 51 | ``` 52 | MinDist ∝ MinDist3D 53 | MinDist(A[1..n]: array of integers) 54 | 1. B[1..n]: array of 3D points 55 | 2. for i<- 1 to n 56 | 3. B[i] <- (A[i],0,0) 57 | 4. d <- MinDist3D(B[1..n]) 58 | 5. return d 59 | ``` 60 | Доказателство за коректност на редукцията: по дефиниция `MinDist3D` ще върне минималното разстояние между някои две точки на различни позиции в масива B, т.е. min{sqrt((xi-xj)2 + (yi-yj)2 + (zi-zj)2) | i!=j}. Тъй като по построение за всяка точка B[i] в масива yi = zi = 0 и xi = A[i], то това минимално разстояние е равно на min{|A[i]-A[j]| | i!=j}. По дефиниция това е именно минималната разлика между два елемента в подадения масив A[1..n], и алгоритъмът MinDist връща точно тази стойност => алгоритъмът работи коректно. 61 | 62 | Ако задачата MinDist3D се решава за o(nlgn), то и задачата MinDist ще се решава за o(nlgn) + O(n), което пак е o(nlgn) време. Това е в противоречие с доказаната сложност по време на MinDist от Ω(nlgn) => не е възможно MinDist3D да се решава за o(nlgn) и това е теоретичната долна граница - също Ω(nlgn). 63 | -------------------------------------------------------------------------------- /Product.md: -------------------------------------------------------------------------------- 1 | Докажете, че следният алгоритъм връща произведението на всички числа в дадения масив: 2 | ``` 3 | Product(A[1..n] : array of integers, n >= 2) 4 | 1. i <- 1, j <- n, p <- 1 5 | 2. while i < j 6 | 3. p <- p*A[i]*A[j] 7 | 4. i <- i + 1, j <- j - 1 8 | 5. if n is odd 9 | 6. p <- p*A[(n+1)/2] 10 | 7. return p 11 | ``` 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AlgorithmNotes 2 | Записки и полезни фрагменти код за упражненията по Дизайн и Анализ на Алгоритми, ~~2017~~ ~~2018~~ ~~2019~~ ~~2020~~ ~~2022~~ ~~2023~~ ~~2024~~ **2025г.** 3 | -------------------------------------------------------------------------------- /UnionFind.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include // size_t 4 | #include // std::is_unsigned_v 5 | 6 | template 7 | class UnionFind { 8 | static_assert(std::is_unsigned_v); 9 | struct pair { Idx p, r; }; 10 | std::vector values; 11 | 12 | // Ease of access, will be inlined 13 | Idx& parent(Idx x) { return values[x].p; } 14 | Idx& rank(Idx x) { return values[x].r; } 15 | 16 | // Find w/ path halving. May probably be optimized a little more. 17 | Idx Find(Idx x) { 18 | while (parent(x) != x) { 19 | parent(x) = parent(parent(x)); 20 | x = parent(x); 21 | } 22 | return x; 23 | } 24 | public: 25 | // Constructs a disjont-set with n values, each in its own set 26 | explicit UnionFind(const Idx n) : values{ n } { 27 | for (Idx i = 0; i < n; ++i) { 28 | parent(i) = i; 29 | rank(i) = 0; 30 | } 31 | } 32 | // Union by rank. Returns true on successful union, 33 | // false if x and y are already in the same component. 34 | bool Union(Idx x, Idx y) { 35 | Idx xroot = Find(x), yroot = Find(y); 36 | if (xroot == yroot) 37 | return false; 38 | if (rank(xroot) < rank(yroot)) 39 | std::swap(xroot, yroot); 40 | parent(yroot) = xroot; 41 | Idx& xrank = rank(xroot); 42 | if (xrank == rank(yroot)) 43 | ++xrank; 44 | return true; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /ex03-extratasks.md: -------------------------------------------------------------------------------- 1 | ## Зад.1 2 | Докажете или опровергайте всяко едно от следните три свойства. В случай, че някое от тях е грешно, посочете един често срещан клас функции, за които е вярно. 3 | 1. f(n) = Θ(f(n+1)) 4 | 2. f = o(f2) 5 | 3. lgf = o(f) 6 | 7 | ## Зад.2 8 | Подредете в линейна наредба по асимптотично нарастване следните функции: 9 | 1. f1 = nlgn 10 | 2. f2 = (lgn)n 11 | 3. f3 = sqrt(2)lgn 12 | 4. f4 = en 13 | 5. f5 = 22n 14 | 6. f6 = (3/2)n 15 | 7. f7 = n! 16 | 8. f8 = (n+1)! 17 | 9. f9 = 3floor(n) 18 | 10. f10 = n(1/lgn) 19 | 20 | където `sqrt(x)` е корен от `x`, а `floor(x)` е `x` закръглено надолу. 21 | -------------------------------------------------------------------------------- /slopes.md: -------------------------------------------------------------------------------- 1 | ## Брой склонове (и максимална дължина на склон) в масив 2 | 3 | За целите на тази задача подмасив ще наричаме последователност от съседни елементи в масив и ще бележим с `A[i..j]`: подмасивът, включващ елементите от индекс `i` до индекс `j`, включително. Под "склон" в масив ще наричаме максимален по включване подмасив, за който всяко следващо число е не по-малко от предишното. Например масивът `А={5,7,7,10,4,5,8}` има точно два склона - подмасивите `A[1..4]` и `A[5..7]` (`{5,7,7,10}` и `{4,5,8}`). `А[5..6]` не е склон, защото може да се "разшири" с елемента `А[7]`, и `A[2..5]` също не е. Очевидно празни склонове няма, а във всеки масив има поне един склон. 4 | 5 | ### Зад.1 6 | 7 | Напишете алгоритъм, който по даден масив от числа намира броя на склоновете в него. Докажете неговата коректност. 8 | Ако желаете, може директно да докажете, че следният алгоритъм решава тази задача: 9 |
10 | цък 11 | Slopes(A[1..n])
12 | 1. count <- 1
13 | 2. for i <- 2 to n
14 | 3. __if A[i] < A[i-1]
15 | 4. ____count <- count + 1
16 | 5. return count 17 |
18 | 19 | ### Зад.2 20 | Напишете алгоритъм, който изчислява максималната дължина на склон в даден масив. Докажете и неговата коректност. 21 | 22 | _Упътване:_ целта на задачата е да използвате доказателство с инварианта на цикъла. Естествено, можете да предложите и рекурсивен алгоритъм :) 23 | --------------------------------------------------------------------------------