Сортуйте кілька яблук!


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 "Що за сенс" - хороший дзвінок. Цього мені не прийшло в голову. Я визначаю, що тут розумним буде менше часу, необхідного для грубої сили рішення;)
Орбі

Чи означає ваше визначення поняття "розумне" спочатку реалізувати грубе рішення, а потім все, що швидше підраховується?
Джон Дворак

Чи важливий остаточний порядок відра?
AMK

Відповіді:


4

Оцінка: 448

Моя ідея - сортувати їх послідовно, починаючи з 1. Це дає нам приємну властивість, що коли ми хочемо перемістити простір до попереднього / наступного кошика, ми точно знаємо, яке з двох яблук ми маємо перемістити - макс / хв. відповідно. Ось розбивка тесту:

#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, схоже, я його позбувся.
ясен

Чи можете ви вказати, що ви змінили формат введення у своєму рішенні, щоб зробити його більш зрозумілим для тих, хто намагається його перевірити?
Орбі

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;}}

І нерозпущений код, який також друкує рахунок:

#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

Це здійснює пошук на глибині спочатку із збільшенням глибини, поки не знайде рішення. Він використовує словник для зберігання відвідуваних станів, щоб він більше не відвідував їх, якщо тільки з вікном більшої глибини. Вирішуючи, які держави перевіряти, він використовує кількість неправильно розміщених елементів як евристичний і відвідує лише найкращі можливі стани. Зауважте, що оскільки порядок елементів у їхньому відрі не має значення, він завжди підтримує впорядкування у відрах. Це полегшує перевірку, чи елемент не розміщений.

Вхід - це масив ints, причому перший 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.