• Как оптимизировать простенький код?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Оптимизировать неправильное решение бесполезно. Тут 2 варианта:
    1) Реализовать бинарный поиск. У вас уже правильная идея, как определить, что за заданное количество секунд можно напечатать сколько надо или больше страниц (floor(seconds/s1)+floor(seconds/s2) >= n). Теперь осталось вместо линейного увеличения seconds, удваивать его. Т.е. вместо seconds = seconds+1 делать seconds = seconds*2. После чего сделать бинарный поиск на отрезке [1,seconds].

    2) Применить математику. Нужно найти минимальное x такое, что floor(x/s1)+floor(x/s2) >= n.

    Забъем на округление и решим x/s1 + x/s2 >= n.
    x = ceil(n*s1*s2/(s1+s2)).

    Но. это будет оценка сверху, потому что мы выкинули округления, которые уменьшают реальное значение количества напечатанных за x секунд страниц. Но, внимание, реальное количество страниц будет максимум на 2 страницы меньше, потому что oкругление сбивает максимум 1 страницу.

    Теперь будем увеличивать x, чтобы увеличить количество напечатанных страниц. Если просто прибавлять 1 каждый раз, то почти всегда занчение не поменяется, потому что x/s1 так и будет дробным. Увеличение произойдет только когда x/s1 или x/s2 будет целым. Следующее такое число можно легко найти (это будет x, делящееся на s1 или s2. Итак, решение:

    def twoprinters(s1,s2,pages):
        seconds= int(pages*s1*s2 + s1 + s2 - 1)/(s1+s2)
        while int(seconds/s1)+int(seconds/s2) < pages:
                a = seconds - seconds%s1 + s1
                b = seconds - seconds%s2 + s2
                seconds = min(a,b)


    Этот код будет работать мгновенно для очень больших чисел. Потому что цикл while тут будет исполнятся максимум 2 раза. Каждый раз значение напечатанных страниц будет увеличиваться как минимум на 1, а оно изначально будет максимум на 2 меньше ответа. Странные вычисления a и b - это следующие числа за seconds, которые делятся на s1 и s2 соответственно. Мы вычитаем остаток от деления и получаем предыдущее или равное seconds число, делящееся на s1 и s2. Прибавляя s1 или s2 мы получаем следующее число, как нам и надо.
    Ответ написан
    Комментировать
  • Наиболее эффективная реализация алгоритмов преобразования BWT и MTF?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вот тут посмотрите: https://stackoverflow.com/questions/7857674/whats-...

    Идея - построить суффиксный массив (что почти тоже самое, что BWT). Там рекомендуют libdivsufsort. Вроде как, его можно использовать и в node.js: https://www.npmjs.com/package/divsufsort
    Ответ написан
    Комментировать
  • Алгоритм решения для игры Unblock Me для c#?

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

    Теперь детали. Сам граф хранить не надо. Надо только уметь как-то хранить, что некоторое состояние поля уже было просмотрено (если нужно еще и сами ходы решения найти, то нужно еще хранить для каждого состояния предшествующее ему в пути). Тут подойдет какой-нибуть hash map или map.

    Для обхода в ширину понадобится еще и очередь. Осталось только научиться по заданному состоянию получать все, куда можно из него перейти за один ход. Для этого состоянием удобно считать не матрицу прямоугольные - а набор чисел: для каждой машинки где в своей строке/столбце она находится. По такому состоянию очень просто восстановить саму матрицу. Потом просто перебираем каждую машинку и все возможные ее сдвиги. Смотрим в матрице, что все клетки нового положения ничем другим не заняты. Если все хорошо - вот новое состояние.

    На практике это будет работать довольно быстро. Если машинок много - то допустимых состояний будет мало. Если же машинок мало, то ответ найдется довольно быстро, а значит просмотрено будет не так много состояний. Проблема только, если решения не существует, а машинок не слишком много. Тогда этот алгоритм будет работать медленно. Но, оценка сверху - N^K состояний, где N - размер поля, а K - количество машинок. Для небольших полей (N=6) будет решать все мгновенно.
    Ответ написан
    Комментировать
  • Как сделать алгоритм Дейкстры параллельным?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Если алгоритм, как у вас, реализован без кучи, то можно втупую распаралелить 2 основные функции - getNodeWithMinimalDistanceToSourceFromUnsettledNodes и findMinimalDistancesFromNodeToNeighbors.
    И там, и там - один цикл. Если количество потоков не слишком большое, то можно достичь почти полного ускорения, загрузив все ядра. Первая функция - просто каждый поток ищет минимум на своем подотрезке массива. В конце, основной поток из этих нескольких значений берет минимум. Вторая функция так же параллелится - каждый поток обновляет расстояния для некоторой части всех соседей (соседи должны в этом случае храниться в массиве).

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

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

    Если массивы не упорядочены, или очень хочется деревом, то
    1) Строим суффиксное дерево для массива a[].
    2) Проходим по всем элементам массива b[] и смотрим, сколько раз подстрока из этого 1 элемента встречается в строке a[]. Это стандартная операция для суф. дерева: надо просто взять количество листьев в поддереве вершины, которая читается искомой строкой (в нашем случае - это непосредственный ребенок корня, к которому ведет строка, начинающаяся с символа b[i]).

    Еще конкретнее - постройте дерево, найдите для каждой вершины количество листьев в поддереве (просто суммируя эти же значения для всех детей или рекурсивно или поднимаясь снизу-вверх). Потом просуммируйте эти значения для всех вершин, в которые из корня есть ребро начинающееся с символа b[i] для всех i.
    Ответ написан
    Комментировать
  • Как проверяется принадлежность точки многоугольнику методом углов?

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

    Для этого нужно взять векторное и скалярные произведения векторов на точки - это будут синус и косинус угла, умноженные на длины векторов. Их, даже не деля на длины, можно передать в функцию atan2(), и она даст вам значение угла. уже с правильным знаком. Хотя можно подстраховаться - угол должен быть от -пи до +пи. Если он больше пи, то нужно вычесть из него 2пи, если меньше -пи, то прибавить.

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Это перефразированная задача о рюкзаке. Надо только заметить, что в оптимальном ответе будет набрано менее K+100 очков. Иначе можно безболезненно выкинуть какую-нибудь миссию (а они все <100 секунд занимают).

    Решение динамическим программированием. Не буду тут повторять - оно простое и везде расписано. Еще раз напомню ключевые слова - "задача о рюкзаке".
    Теперь цена предмета в задаче о рюкзаке - это время, а вес предмета - это очки. Заполняем DP[i] - минимальное время за которое можно набрать i очков. Потом ищем минимум на отрезке [k..k+100). Это и есть ответ к задаче.
    Ответ написан
    Комментировать
  • Есть ли способ автоматически вывести сложность алгоритма (Python)?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Нет нельзя. Потому что нельзя даже определить, завершается ли программа, или виснет. Это называется проблема остановки (https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D... Логически доказано, что невозможно автоматически решить ее.

    Подобным образом можно доказать, что не существует программы, определяющей сложность любой программы.
    Допустим, что такая программа есть. Напишем другую программу, которая анализирует входной исходный код этой первой программой (пусть она получила оценку f(N)), а потом делает что-то неважное (например прибавляет 1 к переменной) f(N)*N раз. Теперь запустим эту программу на собственном исходном коде. Она получит оценку своей сложности и потом сделает что-то в N раз больше. Т.е. фактически сделано f(N)*N операций, но программа же оценила этот код как O(f(N)) на этих входных данных, что неверно.

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Правильный ответ - 52-<длина наибольшей общей подпоследовательности>.
    Выводится он так:
    1) Ни одну карту нет смысла двигать 2 раза. Т.е. ответ 52-<количество неподвижных карт>
    2) Неподвижные карты находятся в том же порядке в обеих колодах, следовательно они образуют наибольшую общую подпоследовательность.

    Наибольшая общая подпоследовательность - вообще-то это их обработки строчек тема, будем считать что у нас даны 2 52-символьные строки, один символ-одна карта, все символы разные. НОП - это такая наибольшая строка, которая останется, если выкинуть из первой и второй строки какие-то символы. Ищется она за квадратичную сложность (в данном случае - моментально, строчки-то фиксированной длины) динамическим программированием.

    F(i, j) - длина наибольшей общей подстроки у префиксов длины i у первой строки и длины j у второй.

    База F(0,*)=F(*,0)=0

    Переход: F(i,j) = max(F(i-1,j), F(i,j-1), F(i-1,j-1)+1). Третий вариант рассматривается только, если i-ый символ в первой строке и j-ый во второй равны.

    Ответ - 52-F(52,52) для вашей задачи

    Если надо выдать куда что перемещать, то задача чуть сложнее. Динамика модифицируется сохранением, какой из трех вариантов был выбран, чтобы потом восстановить НОП. Эти карты помечаются как неподвижные. Потом, для всех карт во второй колоде найдите их места в первой колоде. А потом, тупо, перемещайте подвижные карты по одной на нужные места в возрастающем порядке результирующих мест. Тогда не надо будет вообще учитывать пока не перемещенные карты.
    Ответ написан
    Комментировать
  • Как изменить обычную сортировку вставками так, чтобы алгоритм использовал бинарный поиск?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Бесполезно прикручивать к сортировке вставками бинарный поиск. Кроме поиска места, куда вставляется новый элемент - надо еще и этот элемент вставить. При этом массиве придется сдвигать O(n) элементов при каждой вставке. В связном списке не пришлось бы сдвигать элементы, но там невозможно сделать бинарный поиск, потому что нет быстрого произвольного посика.

    Приведенная вами реализация - довольно быстрая. Тут поиск места вставки и сдвиги всех элементов объединены. Теоретически, можно сначала сделать бинарный поиск и вместо цикла while использовать, допустим, цикл for с фиксированным количеством итераций. Это сократит количество сравнений в цикле 2 раза, но добавит сколько-то сравнений и нелокальных обращений к памяти в бин.поиске. Возможно будет работать быстрее только если входные данные почти в обратном порядке заданы. В среднем же, будет работать хуже.

    Гораздо лучшая реализация была бы изначально зарезервировать первый элемент в массиве и записать туда очень маленькое число. А сами данные записывать после него. Тогда в цикле while не надо будет сравнивать index>0. Эта реализация будет гораздо быстрее дополнительного бинарного поиска.
    Ответ написан
    2 комментария
  • Как правильно реализовать поворот и движение фигуры?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Это базовые элементарные вещи - используйте тригонометрию. Если направление d градусов (от горизонтальной оси) и вам надо сдвинутся вдоль него на расстояние s, то изменения по осям:

    dx = s*cos(d);
    dy = s*sin(d);

    Обычно, аргументы библиотечных функций cos и sin задаются в радианах. Так что, если у вас d в градусах -то его надо домножить на Pi/180.0

    Эти же синусы и косинусы нужно использовать для отрисовки треугольника. Если, координаты центра x0, y0 - то координаты вершины-острия будут
    x0+triangle_size*cos(d);
    y0+triangle_size*sin(d);

    Две другие вершины строятся точно так же, но, для вытянутой формы нужна другая константа множитель и углы там будут d+120.0 и d-120.0 (разные углы дадут разную форму треугольника).

    Соответственно, алгоритм - при нажатии кнопки поворота увеличиваете или уменьшаете d. При нажатии кнопки движения сдвигаете координаты треугольника на (dx, dy).

    Более продвинутые и быстрые решения используют матрицы поворота. Но проще всего это будет понять так - вы не считаете cos(d) и sin(d) каждый раз. А храните их значения. При повороте на угол alpha вам надо будет подсчитать новые значения, используя стандартные тригонометрические формулы из старых значений:

    cos(d+alpha) = cos(d)*cos(alpha) - sin(d)*sin(alpha);
    sin(d+alpha) = cos(d)*sin(alpha) + sin(d)*cos(alpha);

    Прелесть этого метода в том, что при фиксированном шаге изменения угла alpha - вам нужно подсчитать синус и косинус только один раз для alpha, и потом использовать их везде при пересчете.
    Ответ написан
    Комментировать
  • Как работает quicksort?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    1) В коде ошибка - два последних if-а, с рекурсивными вызовами, должны быть за while (i <= j), а не в нем.

    Этот while-цикл - совершает разбиение элементов так, что элементы в s..j - меньше pivot, а в i..f - больше.
    Поддерживается инвариант, что слева от i - только элементы меньше-равные pivot, а справа от j - больше-равные.
    Мы вообще игнорируем, где в массиве pivot, берем только его значение. Неважно в центре он, или вообще первый элемент в массиве.

    Сначала в цикле двигаем i до упора, пропуская меньшие элементы. Обратите внимание - сравнение строгое. По инварианту, правее j - элементы большие-равные pivot, поэтому цикл остановится всегда, если было хоть раз сдвинуто на предыдущей итерации внешнего цикла while. В противном случае - это первая итерация - и где-то в массиве есть сам pivot элемент - на нем цикл точно остановится.

    Потом делаем то же самое но с другой стороны, уменьшая j. Теперь у нас следующая ситуация: arr[s..i-1
    ]<=pivot, arr[i]>=pivot, ???, arr[j]<=pivot, arr[j+1..f]>=pivot. После помены i-того и j-того элементов местами, мы можем сдвинуть i и j на еще одну позицию - ведь теперь новый arr[i]<=pivot, что мы и должны поддерживать в инварианте.

    2 крайних случая: если i==j, то, очевидно, arr[i]==arr[j]=pivot, и можно без нарушения инварианта сдвинуть указатели. Если i>j после вложенных while - это говорит о том, что arr[j..i] == pivot, arr[j-1]<=pivot, arr[i+1]>=pivot, и можно не сортировать часть массива между j и i.

    Отвечая на ваш вопрос: Если слева от pivot недостаточно элементов, то i будет указывать на pivot - и при swap-е сдвинет pivot в правый конец, за j. После этого момента между i и j может вообще не быть элемента равного pivot. Но левее i и правее j Будут те элементы, на которых циклы точно остановятся.
    Ответ написан
    Комментировать
  • Структура данных для хранения точек на карте

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Для вашей задачи подойдет и квадродерево: ru.wikipedia.org/wiki/%D0%94%D0%B5%D1%80%D0%B5%D0%...
    Поиск ближайшей точки к заданной выполняется рекурсивным перебором с отсечениями (не стоит рассматривать квадрат, если он целиком дальше, чем имеющийся промежуточный ответ).
    Поиск всех точек, которые надо вывести делается так же рекурсивно.

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

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Простое, но медленное решение - проверять на пересечение все пары отрезков. Это работает за квадратичную сложность от количества отрезков в контуре.
    Проверка пересечения двух отрезков делается довольно просто. Можно пересечь 2 прямые, заданные параметрически (2 линейных уравнения, решение на бумажке), а потом проверить, что точка пересечения на каждой прямой внутри отрезка (можно задать параметрическое уравнение прямой так, что отрезок соответствует параметрам от 0 до 1). Другой, более быстрый способ с помощью векторного произведения. Надо проверить, что точки каждого отрезка лежат по разные стороны от прямой, на которой лежит другой отрезок.

    Есть сложное, но более быстрое решение задачи - с помощью заметающей прямой. Подробно описанно вот тут: e-maxx.ru/algo/intersecting_segments
    Работает за O(n log n), где n - количество отрезков. Его, правда, придется чуть-чуть подкорректировать, чтобы не учитывать пересечения соседних отрезков.
    Ответ написан
    Комментировать
  • Как найти кратчайший путь в динамическом графе?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Утверждение: Рассмотрим какую-то вершину v. Допустим самый ранний момент, в который можно попасть в нее - t. Если эта вершина есть в кратчайшем пути из вершины a в вершину b, то обязательно есть путь, не длинее, в котором вершина v посещается как раз в момент t.
    Такой путь можно получить конструктивно - начало взять из кратчайшего пути в v, а конец из кратчайшего пути из a в b, может придется подождать какое-то время в вершине, прежде чем продолжить путь.

    С учетом этого утверждения становится понятно, что можно решать вашу задачу стандартным алгоритмом дейкстры для поиска самого быстрого пути во все вершины. Там при релаксации (пересчете минимального растояния) надо просто учитывать, в какое ближайшее к текущему веремни ребро станет доступно для прохождения.
    Ответ написан
    Комментировать
  • Игры на алгоритмы

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