@bigburn
Делаю неживое живым

Какой алгоритм использовать для определения минимального числа перестановок карт в колоде?

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

PS: будет здорово понять, как такое можно реализовать хотя бы в общих чертах. Это нужно для скрипта, который пишу на lua, но язык, мне кажется здесь не принципиален, главное бы понять идею алгоритма
  • Вопрос задан
  • 348 просмотров
Решения вопроса 1
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) для вашей задачи

Если надо выдать куда что перемещать, то задача чуть сложнее. Динамика модифицируется сохранением, какой из трех вариантов был выбран, чтобы потом восстановить НОП. Эти карты помечаются как неподвижные. Потом, для всех карт во второй колоде найдите их места в первой колоде. А потом, тупо, перемещайте подвижные карты по одной на нужные места в возрастающем порядке результирующих мест. Тогда не надо будет вообще учитывать пока не перемещенные карты.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
Мои мысли на тему и получившийся алгоритм:
- обозначим для простоты карты их порядковыми номерами в отсортированной колоде, Ai;
- каждой карте сопоставим расстояние до её конечной желаемой позиции, равное i - Ai;
- перестановка карты с позиции i на позицию j меняет её расстояние на j - i, и одновременно на sign(i - j) меняет расстояния карт между i и j;
- перестановка выгодна если она уменьшает суммарное расстояние, которое потребуется пройти картам после неё;
- для текущей позиции существует по меньшей мере одна наиболее выгодная перестановка -- такая, которая максимально уменьшает оставшееся расстояние; найти её можно тупым перебором исходной и конечной позиции переставляемой карты (но, может быть, можно и быстрее);
- совершая на каждом шаге наиболее выгодную перестановку мы, скорее всего, упорядочим карты за минимальное число перестановок.

Код на c:
#include <limits.h>
#include <stdio.h>
#include <string.h>

#define N (sizeof(a) / sizeof(a[0]))

inline int sign(int d)
{
        if (d == 0)
                return 0;
        return d < 0 ? -1 : 1;
}

inline int abs(int v)
{
        return v < 0 ? -v : v;
}

int main()
{
        int a[] = {2, 1, 3, 4, 0};
        int q[N];

        for (;;) {
                int best_profit = INT_MIN;
                int best_src = -1;
                int best_dst = -1;
                int i, src, dst, tmp;

                for (i = 0; i < N; ++i)
                        q[i] = i - a[i];

                for (src = 0; src < N; ++src)
                        for (dst = 0; dst < N; ++dst) {
                                int d = sign(dst - src);
                                int profit = abs(q[src]) - abs(q[src] + dst - src);

                                //printf("...%d -> %d: profit = %d", src, dst, profit);

                                for (i = src + d; i != dst + d; i += d) {
                                        if (sign(q[i]) == d) {
                                                ++profit;
                                                //printf(" + 1");
                                        } else {
                                                --profit;
                                                //printf(" - 1");
                                        }
                                }
                                //printf(" = %d\n", profit);
                                if (profit > best_profit) {
                                        best_src = src;
                                        best_dst = dst;
                                        best_profit = profit;
                                        //printf("... -- new best!\n");
                                }
                        }
                printf("%d -> %d (profit = %d)\n", best_src, best_dst, best_profit);
                if (best_profit == 0)
                        break;
                tmp = a[best_src];
                if (best_dst < best_src)
                        memmove(a + best_dst + 1, a + best_dst, (best_src - best_dst) * sizeof(int));
                else
                        memmove(a + best_src, a + best_src + 1, (best_dst - best_src) * sizeof(int));
                a[best_dst] = tmp;

                for (i = 0; i < N; ++i)
                        printf("%d ", a[i]);
                printf("\n");
        }
        return 0;
}
Ответ написан
Комментировать
По какому критерию Вы оптимизируете ? По количеству перестановок ?
Потому как, если оптимизация не требуется, то просто за 52 перестановки Вы можете расставить карты на свои места всегда : либо устанавливая карту в первую позицию (доставая в инверсном), либо устанавливая карту в i-ую позицию (доставая в прямом).
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы