Triez quelques pommes!

11

Problème

Imaginez 7 seaux alignés d'affilée. Chaque seau peut contenir au maximum 2 pommes. Il y a 13 pommes étiquetées de 1 à 13. Elles sont réparties entre les 7 seaux. Par exemple,

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

Où 0 représente l'espace vide. L'ordre dans lequel les pommes apparaissent dans chaque seau n'est pas pertinent (par exemple {5,4} équivaut à {4,5}).

Vous pouvez déplacer n'importe quelle pomme d'un seau vers un seau adjacent, à condition qu'il y ait de la place dans le seau de destination pour une autre pomme. Chaque mouvement est décrit par le numéro de la pomme que vous souhaitez déplacer (ce qui est sans ambiguïté car il n'y a qu'un seul espace vide). Par exemple, appliquer le déplacement

7

l'accord ci-dessus entraînerait

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

Objectif

Écrire un programme qui lit un arrangement de STDIN et le trie dans l'arrangement suivant

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

en utilisant le moins de coups possible. Encore une fois, l'ordre dans lequel les pommes apparaissent dans chaque seau n'est pas pertinent. L'ordre des godets est important. Il doit afficher les mouvements utilisés pour trier chaque arrangement séparé par des virgules. Par exemple,

13, 7, 6, ...

Votre score est égal à la somme du nombre de coups requis pour résoudre les arrangements suivants:

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

Oui, chacun de ces arrangements a une solution.

Règles

  • Votre solution doit s'exécuter en temps polynomial en nombre de compartiments par déplacement. Le but est d'utiliser des heuristiques intelligentes.
  • Tous les algorithmes doivent être déterministes.
  • En cas d'égalité, le nombre d'octets le plus court l'emporte.
Orby
la source
2
Quel est l'intérêt de désigner la destination quand il n'y a qu'un seul espace vers lequel vous pouvez déplacer une pomme?
John Dvorak
Que faire si ma solution de force brute s'exécute dans un délai raisonnable? Il n'y a que 700 millions d'États - facilement énumérables en quelques minutes. Définissez «un délai raisonnable».
John Dvorak
@JanDvorak Per "Quel est le point" - bon appel. Cela ne m'était pas venu à l'esprit. Je définis raisonnable ici comme étant inférieur au temps requis pour forcer brutalement la solution;)
Orby
Votre définition de «raisonnable» signifie-t-elle que nous devons d'abord mettre en œuvre la solution de force brute, puis tout ce qui est plus rapide compte?
John Dvorak
L'ordre final du godet est-il important?
AMK

Réponses:

4

Résultat: 448

Mon idée est de les trier consécutivement, en commençant par 1. Cela nous donne la belle propriété que lorsque nous voulons déplacer l'espace vers le panier précédent / suivant, nous savons exactement laquelle des deux pommes nous devons déplacer - le max / min un, respectivement. Voici la répartition des tests:

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

Total score: 448 moves

Le code peut être joué beaucoup plus, mais une meilleure qualité du code motivera des réponses supplémentaires.

C ++ (501 octets)

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

D'autres améliorations peuvent être de passer à C et d'essayer de réduire le score en commençant par les grandes valeurs vers le bas (et éventuellement en combinant les deux solutions).

yasen
la source
1
Une sous-chaîne de votre code forme déjà un programme C. Plus précisément, cela pourrait fonctionner en C en supprimant simplement la première ligne.
feersum
@feersum Vous avez raison. Au début, j'avais plus de code spécifique C ++, mais après cela avec le passage au C à l'esprit, il semble que je m'en sois débarrassé.
yasen
Pourriez-vous spécifier que vous avez modifié le format d'entrée dans votre solution pour le rendre plus clair pour ceux qui essaient de le vérifier?
Orby
2

C, 426 448

Cela trie les pommes une à la fois de 1 à 13, comme dans la méthode de Yasen , sauf si elle a la possibilité de déplacer un plus grand nombre vers le haut ou un plus petit nombre vers le bas, elle le prendra. Malheureusement, cela n'améliore que les performances sur le premier problème de test, mais c'est une petite amélioration. J'ai fait une erreur lors de l'exécution des problèmes de test. Il semble que j'ai simplement réimplémenté la méthode de Yasen.

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

Il prend l'entrée sans accolades ni virgules, par exemple

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

Voici le code golfé à 423 octets comptant quelques nouvelles lignes inutiles (pourrait probablement être joué plus, mais je m'attends à ce que ce score soit battu):

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

Et le code non golfé, qui imprime également la partition:

#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);
}
Orby
la source
2

Python 3-121

Cela implémente une recherche en profondeur d'abord avec une profondeur croissante jusqu'à ce qu'elle trouve une solution. Il utilise un dictionnaire pour stocker les états visités afin qu'il ne les visite plus à moins qu'avec une fenêtre de profondeur plus élevée. Pour décider des états à vérifier, il utilise le nombre d'éléments mal placés comme heuristique et ne visite que les meilleurs états possibles. Notez que puisque l'ordre des éléments dans leur compartiment n'a pas d'importance, il maintient toujours un ordre dans les compartiments. Cela permet de vérifier plus facilement si un élément est mal placé.

L'entrée est un tableau d'entiers, le premier entier étant le nombre de compartiments.

Ainsi, par exemple, pour # 8 (celui-ci prend très longtemps à fonctionner sur ma machine, les autres finissent en quelques secondes):

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

Voici les résultats sur l'ensemble de test: # 1: 12, # 2: 12, # 3: 12, # 4: 12, # 5: 11, # 6: 11, # 7: 10, # 8: 14, # 9: 13, # 10: 14

Voici le code:

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
la source
J'ai voté contre cela parce qu'il est utile de voir des solutions optimales, mais notez que cela ne fonctionne pas en temps polynomial dans le nombre de compartiments par mouvement comme requis par la question. Je ne crois pas qu'un algorithme qui produit une solution optimale (en général) puisse fonctionner en temps polynomial.
Orby
Pour le premier problème de test, votre programme génère 10, 8, 1, 12, 6, 7, 11, 3, 5, 13, 4, 9, ce qui n'est pas une solution valide. Je pense que vous avez peut-être mal compris la question. Notez que la question indique que "vous pouvez déplacer n'importe quelle pomme d'un seau à un seau adjacent", c'est-à-dire le seau à droite ou à gauche (pas un seau arbitraire).
Orby
Oh, j'ai totalement raté la restriction de contiguïté! Après avoir posté cela, j'avais un soupçon persistant que la restriction de durée de fonctionnement avait également été violée. Je n'étais pas sûr à 100% pendant que je l'écrivais, car l'élément de programmation dynamique d'éviter les états répétés m'a dérouté. Merci pour le vote positif même si cela échoue sur deux points; c'est un puzzle amusant et je vais voir si je peux trouver une meilleure réponse valable.
RT