Classifique algumas maçãs!

11

Problema

Imagine 7 baldes alinhados em uma fileira. Cada balde pode conter no máximo 2 maçãs. Existem 13 maçãs rotuladas de 1 a 13. Elas são distribuídas entre os 7 baldes. Por exemplo,

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

Onde 0 representa o espaço vazio. A ordem em que as maçãs aparecem em cada balde não é relevante (por exemplo, {5,4} é equivalente a {4,5}).

Você pode mover qualquer maçã de um balde para um balde adjacente, desde que haja espaço no balde de destino para outra maçã. Cada movimento é descrito pelo número da maçã que você deseja mover (o que não é ambíguo porque existe apenas um espaço vazio). Por exemplo, aplicando a movimentação

7

acordo acima resultaria em

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

Objetivo

Escreva um programa que leia um arranjo do STDIN e o classifique no seguinte arranjo

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

usando o mínimo de movimentos possível. Novamente, a ordem em que as maçãs aparecem em cada balde não é relevante. A ordem dos baldes é importante. Ele deve gerar os movimentos usados ​​para classificar cada arranjo separado por vírgulas. Por exemplo,

13, 7, 6, ...

Sua pontuação é igual à soma do número de movimentos necessários para resolver os seguintes arranjos:

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

Sim, cada um desses arranjos tem uma solução.

Regras

  • Sua solução deve ser executada em tempo polinomial no número de buckets por movimentação. O objetivo é usar heurísticas inteligentes.
  • Todos os algoritmos devem ser determinísticos.
  • Em caso de empate, a menor contagem de bytes vence.
Ou pela
fonte
2
Qual é o sentido de indicar o destino quando existe apenas um espaço para o qual você pode mover uma maçã?
John Dvorak
E se minha solução de força bruta for executada em um período de tempo razoável? Existem apenas 700 milhões de estados - facilmente enumeráveis ​​em questão de minutos. Defina "quantidade razoável de tempo".
John Dvorak
@JanDvorak Por "Qual é o objetivo" - boa ligação. Isso não me ocorreu. Estou definindo razoável aqui para ser menos do que a quantidade de tempo necessário para a força bruta a solução;)
Orby
Sua definição de "razoável" significa que devemos primeiro implementar a solução de força bruta, e depois qualquer coisa que seja mais rápida?
John Dvorak
A ordem final do balde é importante?
AMK

Respostas:

4

Pontuação: 448

Minha idéia é classificá-los consecutivamente, começando com 1. Isso nos dá a propriedade agradável de que, quando queremos mover o espaço para a cesta anterior / seguinte, sabemos exatamente qual das duas maçãs que temos para mover - o valor máximo / min um, respectivamente. Aqui está o detalhamento do teste:

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

Total score: 448 moves

O código pode ser jogado muito mais, mas uma melhor qualidade do código motivará respostas adicionais.

C ++ (501 bytes)

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

Melhorias adicionais podem estar mudando para C e tentando diminuir a pontuação, começando dos grandes valores para baixo (e eventualmente combinando as duas soluções).

Ya Sen
fonte
1
Uma substring do seu código já forma um programa C. Especificamente, ele poderia funcionar em C simplesmente excluindo a primeira linha.
feersum
@feersum Você está certo. No começo, eu tinha mais código específico de C ++, mas depois disso, com a mudança para C, parece que me livrei dele.
yasen 13/09/14
Você poderia especificar que alterou o formato de entrada na sua solução para torná-lo mais claro para aqueles que tentam verificar?
Orby
2

C, 426 448

Isso classifica as maçãs uma de cada vez, de 1 a 13, semelhante ao método yasen , exceto quando houver a oportunidade de mover um número maior para cima ou para um número menor, será necessário. Infelizmente, isso apenas melhora o desempenho no primeiro problema de teste, mas é uma pequena melhoria. Cometi um erro ao executar os problemas de teste. Parece que simplesmente reimplementei o método yasen.

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

É necessário entrada sem chaves ou vírgulas, por exemplo

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

Aqui está o código do golfe entrando em 423 bytes, contando algumas linhas desnecessárias (provavelmente poderia ser mais jogado, mas espero que essa pontuação seja superada):

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

E o código não jogado, que também imprime a pontuação:

#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);
}
Ou pela
fonte
2

Python 3-121

Isso implementa uma pesquisa profunda com profundidade crescente até encontrar uma solução. Ele usa um dicionário para armazenar estados visitados, para que não os visite novamente, a menos que com uma janela de maior profundidade. Ao decidir quais estados verificar, ele usa o número de elementos extraviados como uma heurística e visita apenas os melhores estados possíveis. Observe que, como a ordem dos elementos em seu bucket não importa, ele sempre mantém uma ordem dentro dos buckets. Isso facilita verificar se um elemento está extraviado.

A entrada é uma matriz de entradas, com o primeiro int sendo o número de buckets.

Por exemplo, para o número 8 (este demora muito tempo para ser executado na minha máquina, os outros terminam em segundos):

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

Aqui estão os resultados do conjunto de testes: # 1: 12, # 2: 12, # 3: 12, # 4: 12, # 5: 11, # 6: 11, # 7: 10, # 8: 14, # 9: 13, # 10: 14

Aqui está o código:

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
RT
fonte
Fiz uma votação positiva porque é útil encontrar soluções ótimas, mas observe que isso não é executado em tempo polinomial no número de intervalos por movimentação, conforme exigido pela pergunta. Não acredito que qualquer algoritmo que produz uma solução ótima (em geral) possa ser executado em tempo polinomial.
Orby
Para o primeiro problema de teste, seu programa gera 10, 8, 1, 12, 6, 7, 11, 3, 5, 13, 4, 9, o que não é uma solução válida. Eu acho que você pode ter entendido mal a pergunta. Observe que a pergunta afirma que "Você pode mover qualquer maçã de um balde para um balde adjacente", ou seja, o balde para a direita ou esquerda (não um balde arbitrário).
Orby
Ah, eu perdi totalmente a restrição de adjacência! Depois que eu postei isso, eu tinha uma suspeita persistente de que a restrição de tempo de execução também foi violada. Eu não tinha 100% de certeza enquanto escrevia, porque o elemento de programação dinâmica de evitar estados repetidos me confundia. Obrigado pelo voto positivo, mesmo que isso falhe em dois aspectos; esse é um quebra-cabeça divertido e vou ver se consigo encontrar uma resposta melhor e válida.
RT