Сортировка яблок!


11

проблема

Представьте себе 7 ведер в ряд. Каждое ведро может содержать не более 2 яблок. Есть 13 яблок с маркировкой от 1 до 13. Они распределены между 7 ведрами. Например,

{5,4}, {8,10}, {2,9}, {13,3}, {11,7}, {6,0}, {12,1}

Где 0 представляет пустое пространство. Порядок, в котором яблоки появляются в каждом ведре, не имеет значения (например, {5,4} эквивалентно {4,5}).

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

7

к договоренности выше приведет к

{5,4}, {8,10}, {2,9}, {13,3}, {11,0}, {6,7}, {12,1}

Задача

Напишите программу, которая читает расположение из STDIN и сортирует его по следующему расположению

{1,2}, {3,4}, {5,6}, {7,8}, {9,10}, {11,12}, {13,0}

используя как можно меньше ходов. Опять же, порядок, в котором яблоки появляются в каждом ведре, не имеет значения. Порядок ведер имеет значение. Он должен выводить ходы, используемые для сортировки каждого расположения, разделенного запятыми. Например,

13, 7, 6, ...

Ваша оценка равна сумме количества ходов, необходимых для выполнения следующих мероприятий:

{8, 2}, {11, 13}, {3, 12}, {6, 10}, {4, 0}, {1, 7}, {9, 5}
{3, 1}, {6, 9}, {7, 8}, {2, 11}, {10, 5}, {13, 4}, {12, 0}
{0, 2}, {4, 13}, {1, 10}, {11, 6}, {7, 12}, {8, 5}, {9, 3}
{6, 9}, {2, 10}, {7, 4}, {1, 8}, {12, 0}, {5, 11}, {3, 13}
{4, 5}, {10, 3}, {6, 9}, {8, 13}, {0, 2}, {1, 7}, {12, 11}
{4, 2}, {10, 5}, {0, 7}, {9, 8}, {3, 13}, {1, 11}, {6, 12}
{9, 3}, {5, 4}, {0, 6}, {1, 7}, {12, 11}, {10, 2}, {8, 13}
{3, 4}, {10, 9}, {8, 12}, {2, 6}, {5, 1}, {11, 13}, {7, 0}
{10, 0}, {12, 2}, {3, 5}, {9, 11}, {1, 13}, {4, 8}, {7, 6}
{6, 1}, {3, 5}, {11, 12}, {2, 10}, {7, 4}, {13, 8}, {0, 9}

Да, у каждого из этих механизмов есть решение.

правила

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

2
Какой смысл обозначать пункт назначения, когда есть только одно место, куда вы можете переместить яблоко?
Джон Дворжак

Что делать, если мое решение для перебора работает в разумные сроки? Есть только 700 миллионов состояний - их легко перечислить за считанные минуты. Определите «разумное количество времени».
Джон Дворжак

@JanDvorak Per "Какой смысл" - хороший звонок. Это не пришло мне в голову. Я считаю разумным, чтобы здесь было меньше, чем количество времени, необходимое для грубой силы решения;)
Orby

Означает ли ваше определение «разумный», что мы должны сначала реализовать решение «грубой силы», а затем все, что быстрее, считается?
Джон Дворжак

Важен ли окончательный порядок ковша?
AMK

Ответы:


4

Счет: 448

Моя идея состоит в том, чтобы отсортировать их последовательно, начиная с 1. Это дает нам замечательное свойство: когда мы хотим переместить пространство в предыдущую / следующую корзину, мы точно знаем, какое из двух яблок нам нужно переместить - max / мин один соответственно. Вот тестовая разбивка:

#1: 62     #6: 40
#2: 32     #7: 38
#3: 46     #8: 50
#4: 50     #9: 54
#5: 40    #10: 36

Total score: 448 moves

Код может быть намного лучше, но лучшее качество кода будет мотивировать дополнительные ответы.

C ++ (501 байт)

#include <cstdio>
#define S(a,b) a=a^b,b=a^b,a=a^b;
int n=14,a[14],i,j,c,g,p,q;
int l(int x){for(j=0;j<n;++j)if(a[j]==x)return j;}
int sw(int d){
    p=l(0);q=p+d;
    if(a[q]*d>a[q^1]*d)q^=1;
    printf("%d,", a[q]);
    S(a[q],a[p])
}
int main(){
    for(;j<n;scanf("%d", a+j),j++);
    for(;++i<n;){
        c=l(i)/2;g=(i-1)/2;
        if(c-g){
            while(l(0)/2+1<c)sw(2);
            while(l(0)/2>=c)sw(-2);
            while(l(i)/2>g){sw(2);if(l(i)/2>g){sw(-2);sw(-2);}}
        }
    }
}

Дальнейшие улучшения могут заключаться в переключении на C и попытке снизить оценку, начиная с больших значений вниз (и в конечном итоге они объединяют оба решения).


1
Подстрока вашего кода уже формирует C-программу. В частности, он мог бы работать в C, просто удалив первую строку.
Feersum

@feersum Ты прав. Вначале у меня было больше специфичного для C ++ кода, но после этого с учетом перехода на C, кажется, я избавился от него.
Ясен

Не могли бы вы указать, что вы изменили формат ввода в своем решении, чтобы сделать его более понятным для тех, кто пытается это проверить?
Orby

2

С 426 448

Это сортирует яблоки по одному от 1 до 13, аналогично методу Ясена , за исключением тех случаев, когда у него есть возможность переместить большее число или меньшее число вниз, оно возьмет его. К сожалению, это только улучшает производительность при первой проблеме теста, но это небольшое улучшение. Я допустил ошибку при запуске теста проблем. Кажется, я просто переопределил метод Ясена.

#1: 62    #6: 40
#2: 32    #7: 38
#3: 46    #8: 50
#4: 50    #9: 54
#5: 40    #10: 36

Он принимает ввод без скобок или запятых, например

8 2 11 13 3 12 6 10 4 0 1 7 9 5

Вот код для игры в гольф, поступающий на 423 байта с подсчетом нескольких ненужных символов новой строки (возможно, может быть больше в гольфе, но я ожидаю, что этот счет будет побит):

#define N 7
#define F(x,y) for(y=0;y<N*2;y++)if(A[y]==x)break;
#define S(x,y) x=x^y,y=x^y,x=x^y;
#define C(x,y) ((A[x*2]==y)||(A[x*2+1]==y))
A[N*2],i,j,d,t,b,a,n,s,v,u,w,g;main(){for(;i<N*2;i++)scanf("%d",A+i);g=1;while
(!v){F(0,i);b=i/2;F(g,u);w=u/2;d=b<w?1:-1;n=(b+d)*2;a=(b+d)*2+1;if(A[n]>A[a])
S(n,a);t=d-1?a:n;printf("%d,",A[t]);S(A[i],A[t]);while(C((g-1)/2,g))g++;v=1;for
(j=0;j<N*2;j++)if(!C(j/2,(j+1)%(N*2)))v=0;}}

И код ungolfed, который также печатает счет:

#include <stdio.h>
#include <stdlib.h>

#define N 7

int apples[N*2];

int find(int apple)
{
    int i;
    for (i = 0; i < N*2; i++) {
        if (apples[i] == apple)
            return i;
    }    
}

void swap(int i, int j)
{
    int temp;
    temp = apples[i];
    apples[i] = apples[j];
    apples[j] = temp;
}

int contains(int bucket, int apple)
{
    if ((apples[bucket * 2] == apple) || (apples[bucket * 2 + 1] == apple))
        return 1;
    return 0;
}

int is_solved()
{
    int i, j;
    for (i = 0; i < N * 2; i++) {
        j = (i + 1) % (N * 2);
        if (!contains(i / 2, j))
            return 0;
    }
    return 1;
}

int main()
{
    int i, j, dir, bucket, max, min, score;
    int target_i, target_bucket, target;

    /* Read the arrangement */
    for (i = 0; i < N*2; i++) {
        scanf("%d ", apples + i);
    }

    target = 1;
    while (1) {

        i = find(0);
        bucket = i / 2;
        target_i = find(target);
        target_bucket = target_i / 2;

        /* Change the direction of the sort if neccesary */
        if (bucket < target_bucket) dir = 1;
        else dir = -1;

        /* Find the biggest and smallest apple in the next bucket */
        if (apples[(bucket + dir) * 2] < apples[(bucket + dir) * 2 + 1]) {
            min = (bucket + dir) * 2;
            max = (bucket + dir) * 2 + 1;
        } else {
            min = (bucket + dir) * 2 + 1;
            max = (bucket + dir) * 2;
        }

        /* If we're going right, move the smallest apple. Otherwise move the
           biggest apple */
        if (dir == 1) {
            printf("%d, ", apples[min]);
            swap(i, min);
            score++;
        } else {
            printf("%d, ", apples[max]);
            swap(i, max);
            score++;
        }

        /* Find the next apple to sort */
        while (contains((target - 1) / 2, target))
            target++;

        /* If we've solved it then quit */
        if (is_solved())
            break;
    }
    printf("\n");
    printf("%d\n", score);
}

2

Питон 3 - 121

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

Входные данные представляют собой массив целых чисел, причем первым int является количество сегментов.

Так, например, для № 8 (этот занимает очень много времени на моей машине, остальные заканчивают в секундах):

c:\python33\python.exe apples.py 7 3 4 10 9 8 12 2 6 5 1 11 13 7 0

Вот результаты тестового набора: № 1: 12, № 2: 12, № 3: 12, № 4: 12, № 5: 11, № 6: 11, № 7: 10, № 8: 14, № 9: 13, # 10: 14

Вот код:

import sys    

BUCKETS = int(sys.argv[1])    

# cleans a state up so it is in order
def compressState(someState):
  for i in range(BUCKETS):
    if(someState[2*i] > someState[2*i + 1]):
      temp = someState[2*i]
      someState[2*i] = someState[2*i + 1]
      someState[2*i + 1] = temp
  return someState    

state = compressState([int(x) for x in sys.argv[2:]])
print('Starting to solve', state)
WINNINGSTATE = [x for x in range(1, BUCKETS*2 - 1)]
WINNINGSTATE.append(0)
WINNINGSTATE.append(BUCKETS*2 - 1)
maxDepth = 1
winningMoves = []
triedStates = {}    

# does a depth-first search
def doSearch(curState, depthLimit):
  if(curState == WINNINGSTATE):
    return True
  if(depthLimit == 0):
    return False
  myMoves = getMoves(curState)
  statesToVisit = []
  for move in myMoves:
    newState = applyMove(curState, move)
    tns = tuple(newState)
    # do not visit a state again unless it is at a higher depth (more chances to win from it)
    if(not ((tns in triedStates) and (triedStates[tns] >= depthLimit))):
      triedStates[tns] = depthLimit
      statesToVisit.append((move, newState[:], stateScore(newState)))
  statesToVisit.sort(key=lambda stateAndScore: stateAndScore[2])
  for stv in statesToVisit:
    if(stv[2] > statesToVisit[0][2]):
      continue
    if(doSearch(stv[1], depthLimit - 1)):
      winningMoves.insert(0, stv[0])
      return True
  return False    

# gets the moves you can make from a given state
def getMoves(someState):
  # the only not-allowed moves involve the bucket with the 0
  allowedMoves = []
  for i in range(BUCKETS):
    if((someState[2*i] != 0) and (someState[2*i + 1] != 0)):
      allowedMoves.append(someState[2*i])
      allowedMoves.append(someState[2*i + 1])
  return allowedMoves    

# applies a move to a given state, returns a fresh copy of the new state
def applyMove(someState, aMove):
  newState = someState[:]
  for i in range(BUCKETS*2):
    if(newState[i] == 0):
      zIndex = i
    if(newState[i] == aMove):
      mIndex = i
  if(mIndex % 2 == 0):
    newState[mIndex] = 0
  else:
    newState[mIndex] = newState[mIndex-1]
    newState[mIndex-1] = 0
  newState[zIndex] = aMove
  if((zIndex % 2 == 0) and (newState[zIndex] > newState[zIndex+1])):
    newState[zIndex] = newState[zIndex+1]
    newState[zIndex+1] = aMove
  return newState    

# a heuristic for how far this state is from being sorted
def stateScore(someState):
  return sum([1 if someState[i] != WINNINGSTATE[i] else 0 for i in range(BUCKETS*2)])    

# go!
while(True):
  triedStates[tuple(state)] = maxDepth
  print('Trying depth', maxDepth)
  if(doSearch(state, maxDepth)):
    print('winning moves are: ', winningMoves)
    break
  maxDepth += 1

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

Для первой тестовой задачи ваша программа генерирует 10, 8, 1, 12, 6, 7, 11, 3, 5, 13, 4, 9, что не является допустимым решением. Я думаю, что вы, возможно, неправильно поняли вопрос. Заметьте, что вопрос гласит: «Вы можете переместить любое яблоко из одного ведра в соседнее ведро», то есть ведро справа или слева от него (не произвольное ведро).
Орби

О, я полностью пропустил ограничение смежности! После того, как я опубликовал это, у меня появилось ноющее подозрение, что ограничение продолжительности работы также было нарушено. Я не был уверен на 100%, когда писал, потому что элемент динамического программирования, состоящий в том, чтобы избегать повторяющихся состояний, смутил меня. Спасибо за отзыв, хотя это не удается по двум причинам; это забавная головоломка, и я посмотрю, смогу ли я придумать лучший, правильный ответ.
RT
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.