diff --git a/bin/main.cpp b/bin/main.cpp index ad8d61a..ff63bbf 100644 --- a/bin/main.cpp +++ b/bin/main.cpp @@ -1,8 +1,9 @@ #include #include "tasks/001.hpp" +#include "tasks/002.hpp" auto main() -> int { - alg::tasks::run_001(); + alg::tasks::run_002(); } diff --git a/src/tasks/001.hpp b/src/tasks/001.hpp index 2144a59..1717d64 100644 --- a/src/tasks/001.hpp +++ b/src/tasks/001.hpp @@ -29,7 +29,7 @@ namespace alg::tasks std::cout << data[i].second << ' '; // потом нечетные в порядке убывания - for (int i = n - 1- n % 2; i >= 1; i -=2) + for (int i = n - 1 - n % 2; i >= 1; i -= 2) std::cout << data[i].second << ' '; std::cout << std::endl; @@ -37,7 +37,7 @@ namespace alg::tasks for (std::size_t i = 0; i < n; i += 2) std::cout << data[i].first << ' '; - for (int i = n - 1- n % 2; i >= 1; i -=2) + for (int i = n - 1- n % 2; i >= 1; i -= 2) std::cout << data[i].first << ' '; std::cout << std::endl; } diff --git a/src/tasks/002.hpp b/src/tasks/002.hpp new file mode 100644 index 0000000..389d241 --- /dev/null +++ b/src/tasks/002.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace alg::tasks +{ + // Рассмотрим последовательность целых чисел длины n. + // По ней двигается «окно» длины k: сначала в «окне» находятся + // первые k чисел, на следующем шаге в «окне» уже будут находиться + // k чисел, начиная со второго, и так далее до конца последовательности. + // Требуется для каждого положения «окна» определить минимум в нём. + /* пример: + 7 3 + 1 3 2 4 5 3 1 + + Рассмотрим все доступные положения скользящего окна: + |1 3 2| 4 5 3 1 - min(1, 3, 2) = 1 + 1 |3 2 4| 5 3 1 - min(3, 2, 4) = 2 + 1 3 |2 4 5| 3 1 - min(2, 4, 5) = 2 + 1 3 2 |4 5 3| 1 - min(4, 5, 3) = 3 + 1 3 2 4 |5 3 1| - min(5, 3, 1) = 1 + + результат [1, 2, 2, 3, 1] + */ + inline void run_002() + { + // Ускоряем ввод/вывод для больших объёмов данных + std::ios_base::sync_with_stdio(false); + std::cin.tie(nullptr); + + int n, k; + std::cin >> n >> k; + std::vector a(n); + for (int i = 0; i < n; ++i) std::cin >> a[i]; + + // deque хранит индексы элементов текущих окон. + // Значения a[dq[0]], a[dq[1]], ... всегда идут в неубывающем порядке. + std::deque dq; + + for (int i = 0; i < n; ++i) + { + // 1. Удаляем индекс из начала, если он вышел за пределы окна + if (!dq.empty() && dq.front() == i - k) dq.pop_front(); + + // 2. Удаляем индексы с конца, если их значения >= текущего элемента. + // Они больше никогда не станут минимумом окна. + while (!dq.empty() && a[dq.back()] >= a[i]) dq.pop_back(); + + // 3. Добавляем текущий индекс + dq.push_back(i); + + // 4. Если окно полностью сформировалось (прошли минимум k элементов), + // минимум всегда находится в начале deque. + if (i >= k - 1) std::cout << a[dq.front()] << '\n'; + } + } +} + +/* + Основное: + 1. **`dq.front()`** всегда содержит индекс минимального элемента в текущем окне. + 2. При сдвиге окна мы удаляем устаревший индекс слева (`i - k`). + 3. Перед добавлением нового элемента мы "очищаем" хвост очереди от элементов, которые больше или равны новому. + Они бесполезны, так как новый элемент `a[i]` моложе (живёт дольше в окне) и не больше их, поэтому минимумом они стать не смогут. + 4. Каждый элемент добавляется в `deque` ровно один раз и удаляется тоже не более одного + раза → **амортизированное время `O(1)` на шаг**, итого `O(n)` на всю последовательность. + + Сложность: + - **Время:** `O(n)` (проход по массиву, каждое число добавляется/удаляется из `deque` максимум 1 раз) + - **Память:** `O(n)` для хранения массива + `O(k)` для `deque` (в худшем случае) + + Примечание по вводу/выводу: + В коде использованы `ios_base::sync_with_stdio(false); cin.tie(nullptr);` и `'\n'` вместо `endl`. + Это критически важно для задач с `n > 10^5`, иначе программа может не уложиться в лимит времени из-за медленного I/O в C++. +*/ diff --git a/src/utils/find_primes.hpp b/src/utils/find_primes.hpp deleted file mode 100644 index fa8f6ba..0000000 --- a/src/utils/find_primes.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include - -// находит все простые числа от 0 до n -// Решето Эратосфена -namespace alg -{ - inline std::vector find_primes(int n) - { - std::vector r; - std::vector is_prime(n + 1, true); - - // 0 и 1 не являются простыми числами - is_prime[0] = is_prime[1] = false; - - // Исключаем все чётные числа, кроме 2 - for (int i = 4; i <= n; i += 2) is_prime[i] = false; - - // Перебираем только нечётные числа, начиная с 3 - for (int p = 3; p * p <= n; p += 2) - if (is_prime[p]) - for (int i = p * p; i <= n; i += 2 * p) is_prime[i] = false; - - for (int p = 3; p <= n; p += 2) - if (is_prime[p]) r.push_back(p); - - return r; - } -} - diff --git a/src/utils/gcd.hpp b/src/utils/gcd.hpp deleted file mode 100644 index 871b702..0000000 --- a/src/utils/gcd.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -// Называется алгоритм Евклида -// Находит наибольший общий делитель -// см. Алгоритмический тренинг стр. 80 -namespace alg -{ - inline int gcd(int a, int b) - { - while (b != 0) - { - int r = a%b; - a = b; - b = r; - } - - return a; - } -} diff --git a/src/utils/is_prime.hpp b/src/utils/is_prime.hpp deleted file mode 100644 index 108b6ac..0000000 --- a/src/utils/is_prime.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include "gcd.hpp" - -// Малая теорема Ферма: Если p - число простое и a - целое число, не делящееся на p, то при -// возведении a в степень p - 1, наибольший общий делитель между результатом и p будет равен 1 -// Но там есть ньюансы, называются обманщиками Ферма. По этому тесты нужно проводить на нескольких числах a. -// По этому если тестов много (у нас по уолчанию 1000) то вероятность того что появятся обманщики Ферам 1/2^1000 -// Проверяет число на простоту -namespace alg -{ - inline bool is_prime(int a, int max_test = 1000) - { - // начинаем с 4 т.к. 2 и 3 простые числа - for (int i = 4; i < max_test; ++i) - if (gcd(std::pow(i, a - 1), a) == 1) return true; - return false; - } -} - diff --git a/src/utils/prime_factors.hpp b/src/utils/prime_factors.hpp deleted file mode 100644 index 4247f1c..0000000 --- a/src/utils/prime_factors.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include -#include - -// Находит все простые множители заданного числа. -// Простое число - это число > 1, которое делится на 1 и на само себя. -namespace alg -{ - inline std::vector prime_factors_v1(int a) - { - std::vector result; - int i = 2; - - while (i < a) - { - while (a%i == 0) - { - result.push_back(i); - a /= i; - } - ++i; - } - - if (a > 1) result.push_back(a); - - return result; - } - - inline std::vector prime_factors_v2(int a) - { - std::vector result; - - while(a%2 == 0) - { - result.push_back(2); - a = a/2; - } - - int i = 3; - int max_faxtor = std::sqrt(a); - - while (i <= max_faxtor) - { - while (a%i == 0) - { - result.push_back(i); - a /= i; - max_faxtor = std::sqrt(a); - } - i += 2; - } - - if (a > 1) result.push_back(a); - - return result; - } - -}