Ответы пользователя по тегу Алгоритмы
  • Как работает данный алгоритм проверки числа на простоту и какой у него Big O??

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Это вроде как решето Эратосфена (только с багами).

    Этот цикл по идее помечает составными все числа, делящиеся на i (которое к этому моменту вроде как простое). Таким образом все составные числа должны быть помечены в массиве a.
    Тут есть оптимизация: помечаются только те числа, для которых i - делитель не больше корня. Иначе бы цикл был не от i*i а от i. Эту оптимизацию можно делать, потому что у каждого числа обязательно есть простой делитель не больше корня, а значит и такой цикл пометит все составные числа.

    Сложность тут O(n log(log n)) - Доказательство смотрите в википедии.
    Ответ написан
    Комментировать
  • Как классифицировать числовой ряд?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Это называется кластеризация. Вам надо разбить данные на 3 кластера. Метод ближайших соседей, упомянутый выше, подходит.

    Все-таки все эти методы кластеризации разработаны для более общего случая. А у вас тут данные одномерные и численные уже - так что все совсем просто.

    Во-первых, данные надо отсортировать, если уже не. А дальше у вас тут 2 переменные - (i,j) - первый и последний элемент в средней группе.i>0,j<n-1.

    Можно эти индексы тупо перебрать двумя вложенными циклами. А дальше надо считать какую-то целевую функцию - насколько хорошо кластеры выбраны и брать лучшее значение. Надо, чтобы внутри различие было поменьше, а между кластерами - побольше. Можно брать максимум разности в каждом кластере и делить на разницу между двума кластерами. Вот минимизируйте эту функцию.

    Но вообще можно, наверно, просто взять 2 максимальных промежутка между соседними числами и по ним разделить. В примере выше этот метод отлично разбивает на 3 группы: 1-103, 999-1001, 9000-9500

    Если разность между маленькими числами важнее разности между большими, то возьмите сначала логарифм от всех чисел и уже это разбивайте на группы.
    Ответ написан
    7 комментариев
  • Как найти такую вершину заданного графа, которая принадлежит каждому пути между двумя выделенными?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Все такие вершины являются точками сочленения. Ведь, удалив такую вершину из графа больше не будет пути между двумя вершинами, а значит граф развалился на новые компоненты связности. Есть алгоритм на основе Dfs, который их ищет. Это все работает за O(V+E). Предложенный вами алгоритм с удалением по одной вершине вполне себе работает, но будет медленнее: O(V(V+E)). Для небольших графов может и подойдет.

    Потом надо найти путь между заданными вершинами и вывести в ответ точки сочленения. Лучше ищите путь тем же DFSом, что и ищите точки сочленения. Так путь будет идти по дереву DFS. Надо найти в нем LCA двух искомых точек (Пик пути, после которого он пойдет вниз в другую ветку).

    Важно, выводить надо не все точки сочленения, а только те, у которых из следующей вершины в пути, она же будет ребенком в дереве DFS, нельзя вернутся назад выше текущей точки сочленения. Т.е. точка назначена алгоритмом точкой сочленения из-за ребенка именно в сторону искомой вершины.
    Ответ написан
    Комментировать
  • Как растягивать прямоугольники до полного замощения области?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Не знаю, насколько оптимальное решение это будет давать, но раздувайте прямоугольники по чуть-чуть.

    Пусть у прямоугольников будут степени свободы - верх,лево,низ,право. Изначально все прямоугольники раздуваются во все 4 стороны.

    Дальше, представьте, что каждый прямоугольник раздувается на 1 пиксель во все стороны за 1 тик.

    Перебрав все пары прямоугольников вы можете для них подсчитать через какое время эта парочка столкнется. Важно, надо считать только варинаты, где прямоугольники раздуваются друг на встречу другу (хотя бы один из пары), а то у вас может все время быть время 0 - два прямоугольника сейчас касаются.

    Найдите минимальное время для всех пар. "Отмотайте" это время вперед - раздуйте все прямоугольники на это количество пикселей по всем активным степеням свободы. У двух пересекающихся отберите свободу раздуваться в направлениях, где они коснулись. Если они коснулись по углу, то у вас это будет событие: или они встретились по горизонтали, или по вертикали. Именно эти степени свободы и отбираете.

    Повторяйте пока есть хоть одна степень свободы где-то.

    Ну и еще надо учитывать пересечения с гарницей поля - точно так же. Считаете время до столкновения.

    Может быть так, что пройдет время 0, если куча прямоугольников коснутся в одно и то же время. Это нормально, вы не перематывая время в обработке этого события дезактивируете какие-то степени свободы у каких-то прямоугольников.

    Можно просто после каждого столкновения снова искать минимум по всем парам прямоугольников. Можно это соптимизировать знатно через приоритетную очередь, но вряд ли у вас там тысячи прямоугольников и вам это надо делать.

    Еще прикол, если прямоугольники пересекутся через пол тика - между ними всего один пиксель. Тут надо будет двигать только один из двух, скажем, с тот у кого номер меньше.

    В примере с картинки вы через 0 тиков зафиксируете, допустим, вертикальные границы и потом найдете. что через какое-то время один из прямоугольников упрется в стену. Раздуете оба прямоугольника, зафиксируете упершийся. Дальше, если все симметрично, то второй прямоугольник через 0 тиков тоже упрется в стену.

    Правда у этого метода есть проблема Например, вот такая картинка:
    AA.B
    ...B
    C...
    C.DD


    Тут прямоугольники можно чуть чуть раздуть до упирания, но при определенных пропорциях в центре останется пустое место, при чем все 4 окна будут сцеплены между собой. Не уменьшая изначальные окна его никак не убрать.
    Ответ написан
    Комментировать
  • Реализация лабиринта по алгоритму Уилсона. Как сделать это в C# WinForms?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В чем проблема перевести с lua на C#?

    Там в алгоритме ничего сверх сложного и какой-то встроенной библиотеки не используется. Понятно, что MazeGrid - это двумерный массив? Aux - это класс с методами createGrid (возвращает двумерный массив класса с полями visited, right_wall, bottom_wall), wilson (реализация алгоритма) и всякие вспомогателные hashKey/deHashKey. Еще есть поля width/height/sx/sy.

    Сам алгоритм в wilson() использует циклы while и for, if, elseif - все это переводится на C# не задействуя мозг вообще. Чисто механически. Ну, разве что о типах переменных чуть чуть подумать придется. Но, ясно же, что dir - это строка, isMoved - bool.

    Потом, когда вы это все переведете, можно начинать улучшать код. Типа вместо строк для направлений использовать enum.
    Ответ написан
    Комментировать
  • Вычислить исходные величины на основе измерений с погрешностью - как называется и решается такая задача?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Это называется кластеризация.

    Восстановить точное значение вы никогда не сможете, но можно взять среднее в каждом кластере. Такими измерениями занимается статистика
    Ответ написан
    Комментировать
  • Импликация (следование) в C++?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Нету в C++ отдельных операторов для этих логических функций. Потому что их все можно составить из И, ИЛИ и НЕ.

    Так, импликация - это !a || b. Эквивалентность - можно просто сравнить 2 переменные: a == b.
    Ответ написан
    1 комментарий
  • Как написать свое регулярное выражение?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Без теории тут никак.
    Тут 2 варианта: или стройте конечный недетерменированный автомат (с эпсилон переходами), который соответствует этому регулярному выражению и дальше применяйте стандартный алгоритм проверки. что автомат принимает заданную строку. Или второй вариант: пишите динамическое программирование "соответствует ли вот этот префикс заданной строки вот этому префиксу регулярного выражения".

    Конечный автомат будет и побыстрее работать и памяти меньше требовать.

    upd: ну и, конечно, тут полным перебором рекурсивно можно сделать. Но это будет гораздо медленнее любого из указанных выше методов.
    Ответ написан
    2 комментария
  • Как определить ближайших соседей прямоугольника с заданной стороны на поле с пропусками, без полного перебора?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Эта задача решается алгоритмом "сканирующая прямая".

    Представьте что у есть вертикальная линия, которая двигатеся слева-направо непрерывно. В каждый момент времени на прямой могут появиться какие-то прямоугольники, могут закончиться какие-то прямоугольники или ничего не поменяется - в этот момент прямая какие-то прямоугольники пересекает.

    Надо взять какую-то хитрую структуру данных, чтобы хранить все открытые (в данном случае - последние закрытые) прямоугольники на прямой. В моменты открытия/закрытия прямоугольников надо структуру данных обновлять или опрашивать.

    Тут подойдет что-нибудь вроде std::set в C++ - структура хранящая упорядоченное множество объектов, с доступом и изменением за логарифм, умеющая искать ближайший к заданному значению слева и справа (lower_bound, upper_bound). Хранить в ней мы будем вертикальные отрезки, помеченные номерами их прямоугольникв. Не знаю ее аналоги в других языках - нужно что-то реализованное или на binary search tree, или на skip list.

    Когда прямоугольник открывается надо посмотреть, с какими отрезками в set данный отрезок пересекается. Все эти отрезки надо добавить в ответ, удалить полностью покрываемые новым отрезком, а торчащие из него укоротить. Таким образом мы будем поддерживать множество "видимых" отрезков с прямой - ближайших к ней слева.

    В этой задаче можно забить на правые границы прямоугольников и на их ширину (раз нет пересечений). Соседями каждого отрезка - левого края будут ближайшие к нему левые края каких-то прямоугольников.
    Поэтому вам надо получить все отрезки заданные в виде {x, y1, y2, id} и отсортировать их (сначала по x, потом по y). Потом в этом порядке их обходите и применяйте новый отрезок к структуре данных. Все удаляемые отрезки + 2 пересекающихся сверху и снизу пойдут в список соседних для нового отрезка.

    Этот алгоритм за O(n log n) получит всех соседей для всех прямоугольников.
    Это что-то похожее вот на эту задачку с leetcode
    Ответ написан
    2 комментария
  • Min max алгоритм или как сделать ползунок сложности в игре?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Можно при просчете вершины дерева игры выбирать не максимальное/минимальное значение из всех детей, а второе с конца с некоторой вероятностью. Значение вероятности задаётся уровнем сложности.
    Ответ написан
    Комментировать
  • Как превратить подстроку вида "min ( a, b )" в "a min b"?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Решение попроще, за квадрат:
    Пропустили ведущие и trailing пробелы, проверили, что строка начианется с "min" (иначе ничего делать не надо, возвращаем все).
    Потом заведите счетчик открытых скобок, пройдитесь до первой запятой внутри одной пары скобок. Рекурсивно преобразуйте левую часть, добавьте к этому min и результат рекурсивного преобразования правой части (от зяпятой до закрывающей скобки в конце).

    Решение поумнее, за линию:

    Рекурсивная функция будет принимать строку и индекс того места, с которого надо обработать выражение. Возвращать будет результат преобразования и индекс конца обработанного выражения. Важно, обработаться может только часть строки.

    Итак, функция, опять же, проверяет, что есть min. Если нет - возвращает всю строку до первой "," или непарной ")" - нужен счетчик открытых скобок.
    В противном случае, пропускает "min", "(". Рекурсивно преобразовывает от этой позиции. Потом смотрит, что после позиции, которую вернула рекурсивная функция, лежит ",". Иначе - ошибка. Добавляет min к ответу, Рекурсивно преобразовывает оставшееся и добавляет к ответу. Проверяет, что дальше после позиции где закончила обработку рекурсивная функция лежит ")". Возвращает следующую за этим позицию.

    Можно не возвращать преобразованную строку, а сразу все функции могут просто дописывать свой ответ в какое-то одно место. А возвращать int - индекс конца. Ну, или длину обработанной части строки - так даже понятнее.
    Ответ написан
    1 комментарий
  • Где ошибка в задачке с возможными ходами ферзя?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    int(A[i][j]) == 70). Блин. А A[i][j] == 'F' написать религия не позволяет?

    А ошибка в том, что у вас в циклах, которые по диагонали знаки ставят, условие выхода некорректное.
    У вас там <= 8, когда как индексы в матрице от 0 до 7. И при координате равной 8 вы пишите в какую-то левую память. Иногда совпадает, что это начало следующей строки. Иногда вы можете перезаписывать какую-то другую переменную. Вообще, программа может и упасть с ошибкой.
    Ответ написан
    2 комментария
  • Как вычислить количество шагов для вычисления чисел Фибоначчи?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Составьте рекурентное соотношение. Пусть S(k) - сколько шагов надо для вычисления k-ого числа.
    Это будет 1 шаг (сумма в конце), плюс сколько надо шагов, чтобы подсчитать слагаемые. Т.е.:
    S(k) = S(k-1) + S(k-2) + 1
    И известно, что S(1) = S(2) = 0

    Уже можно это все подсчитать, как если бы вы числа фиббоначи считали. Хоть рекурсивно, хоть циклом (что, конечно, быстрее).

    Но можно добавить в обе стороны урванения +1 и сгрупировать слагаемые аккуратно:
    S(k)+1 = S(k-1)+1 + S(k-2)+1

    Тогда, если обозначить G(k) = S(k)+1, то получится:
    G(k) = G(k-1) + G(k-2) и G(1) = G(2) = 1

    Т.е. ответом будет предыдущее число фиббоначи минус один (нам же S(k) = G(k)-1 надо. Плюс, у вас числа с 1,2 начинаются, а тут с 1,1).
    Принципиально от вычислений выше не отличается, но наблюдение интересное.
    Ответ написан
    Комментировать
  • Как записать условие?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Используйте флаги:

    есть_столбец_с_нулями = ложь
    Для каждого столбца j
      количество_нулей = ПодсчитатьКоличествоНулейВСтолбце(j)
      если количество_нулей >= 2 то
        есть_столбец_с_нулями = истина
    
    Если есть_столбец_с_нулями
      НайтиСуммуЭлементовНадДиаганалью();


    Функция для подсчета нулей в столбце простая - пройдитесь циклом по всем элементам столбца (по строкам). Если текущий элемент ноль - то увеличивайте счетчик.

    Можно не писать отдельную функцию а просто воткнуть вложенный цикл.
    Ответ написан
    1 комментарий
  • Как циклом Python for пройти несколько (сотен) range?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Тут уже предложили всякие питонистые подходы через itertools. Но если их не знать, то подойдет и просто 2 вложенных цикла. Внешний перебирает интервал, а внутренний проходит его значения.
    Ответ написан
    Комментировать
  • Пояснения по алгоритму нахождения суммы четырех квадратов?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    1) l не вычисляется. l перебирает некоторые простые числа до log n (2 и те, которые дают остаток 1 при делении на 4).

    2) Процесс описан внизу страницы. Вы представляете n в виде произведения нечетного (и не делящегося на описанные выше простые числа) n' и вот этих всех простых чисел в каких-то степенях. Далее, поскольку мы можем эти простые числа разложить в сумму двух квадратов после предподсчета, то используя описанный в статье ранее трюк можно получить разложение на квадраты n из разложения n'

    3) Это трюк, чтобы все эти простые числа найти до логарифма найти.

    4) Кажется не обязательно и натуральный логарифм тут используется, чтобы оценка сложности была оптимальная. Но я не уверен. Лучше не надо.
    Ответ написан
    Комментировать
  • Можно ли сбалансировать бинарное дерево поиска без использования поворотов дерева?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Можно. Декартово дерево можно реализовать через split/merge. Даже если у вас нет второго ключа, то можно релизовать это храня высоты деревьев или вообще случайно решать, стоит ли вставлять элемент сюда (где вызывать split) и какая из двух вершин станет корнем поддерева при merge.
    Ответ написан
  • Как сделать плавный переход высот в шуме перлина?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Как сделать плавный переход от белого к черному? Нужен серый цвет. Но если у вас дисплей только 2 цвета имеет (а у вас же только снег и камень), то серый цвет можно сделать мешая белые и черные пиксели.

    Например, после какой-то высоты всегда снег. Ниже какой-то высоты всегда камень. А по середине нужно что-то случайное. Можно, например, генерировать 2 шума и сравнивать их как-то. Например, квадрат одного больше другого, умноженного на какую-то константу. С формулами надо поэкперементировать, посмотреть, что хорошо выглядит.
    Ответ написан
    1 комментарий
  • Как из массива целых чисел найти все возможные комбинации (не только двух чисел, а и более) дающие искомую сумму?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Во-первых, таких комбинаций может быть до 2^n, где n - количество чисел в массиве.

    Можно рекурсивно перебрать числа: функция принимает список уже выбранных чисел, их сумму и сколько первых чисел массива обработаны. Если все числа обработаны, функция сравнивает сумму с искомой и, если надо, выводит список. Потом завершается. Если еще не все числа обработанны, то функция два раза рекурсивно вызвается с параметрами: Текущее число добавлено или нет в список, обработано на одно чисел больше.

    Другой вариант, через битовые маски, без рекурсии. Перебирайте число от 0 до 2^n-1. Потом смотрите на него, как на битовую маску. Так вы переберете все подмножества из n элементов. Если i-ый бит установлен, то берите i-ое число в сумму. Если сумма совпала с искомой - вы нашли вариант.

    Ну и самый быстрый вариант: с использованием динамического программирования. Как в задаче о рюкзаке вам надо подсчитать F(i,j) - можно ли числами с i-ого по последнее собрать сумму равную j. Потом рекурсивый перебор оптимизируется с этой информацией. Вы текущее число берете или нет и запускаетесь рекурсивно, если оставшимеся числами можно набрать оставшуюся сумму до ответа.

    В общем случае это решение будет работать все так же экспоненциально. Но, если ответ не большой, т.е. вариантов набрать нужную сумму не много, то это будет сильно быстрее первых двух решений, потому что тупиковые ветки не перебираются.
    Ответ написан
    Комментировать
  • Задача на геометрию. Как быстро найти подходящую выборку элементов из матрицы?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Чтобы расставить жуков оптимально, надо бинпоиском перебрать максимально разрешенное растояние и дальше проверить, что в графе из расстояний, не больше заданного, есть полное паросочетание.

    Еще, угол поворота надо искать троичным поиском, ибо функция не монотонна, а пооизводную фиг найдете. В итоге у вас будет троичный поиск, запускающий двоичный поиск, щапускающий поиск паросочетания.
    Ответ написан