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

    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. Тут не совсем привычное программирование. Надо на плоском поле расставлять в клетках команды. Некоторый манипулятор двигается по полю, повинуясь командам и берет/отпускает атомы. Надо собирать из атомов определенные молекулы. Развивает не только алгоритмический образ мышления, но и воображение и память.
    Ответ написан
    Комментировать