Robots! Recoge estos encurtidos!

10

Parece que me he metido en un aprieto. Literalmente. ¡Dejé caer un montón de pepinillos en el suelo y ahora están todos dispersos! Necesito que me ayudes a recogerlos a todos. Oh, ¿mencioné que tengo un montón de robots a mis órdenes? (También están todos dispersos por todo el lugar; soy realmente malo organizando las cosas).

Debe tomar la entrada en forma de:

P.......
..1..2..
.......P
........
P3PP...4
.......P

es decir, múltiples líneas de ., P(pickle) o un dígito (ID del robot). (Puede suponer que cada línea tiene la misma longitud, rellenada con .). Puede ingresar estas líneas como una matriz, o sorber desde STDIN, o leer en una sola línea separada por comas, o leer un archivo, o hacer lo que quiera Me gusta tomar la entrada.

Su salida debe estar en forma de nlíneas, donde nes la ID de robot más alta. (Las ID del robot siempre serán secuenciales a partir de 1.) Cada línea contendrá la ruta del robot, formada por las letras L(izquierda), R(derecha), U(arriba) y D(abajo). Por ejemplo, aquí hay una salida de ejemplo para ese rompecabezas:

LLU
RDR
LRRR
D

También puede ser

LLU RDR LRRR D

O

["LLU","RDR","LRRR","D"]

O cualquier formato que desee, siempre que pueda saber cuál se supone que es la solución.

Su objetivo es encontrar la salida óptima, que es la que tiene menos pasos. La cantidad de pasos se cuenta como la mayor cantidad de pasos de todos los robots. Por ejemplo, el ejemplo anterior tenía 4 pasos. Tenga en cuenta que puede haber múltiples soluciones, pero solo necesita generar una.

Puntuación:

  • Su programa se ejecutará con cada uno de los 5 casos de prueba (generados aleatoriamente).
  • Debe agregar los pasos de cada ejecución, y ese será su puntaje.
  • Ganará el puntaje total más bajo acumulado.
  • Es posible que no codifique para estas entradas específicas. Su código también debería funcionar para cualquier otra entrada.
  • Los robots pueden pasar unos a otros.
  • Su programa debe ser determinista, es decir, la misma salida para cada ejecución. Puede usar un generador de números aleatorios, siempre que esté sembrado y produzca consistentemente los mismos números multiplataforma.
  • Su código debe ejecutarse en 3 minutos para cada una de las entradas. (Preferiblemente mucho menos).
  • En caso de empate, la mayoría de los votos positivos ganarán.

Aquí están los casos de prueba. Se generaron aleatoriamente con un pequeño script Ruby que escribí.

P.......1.
..........
P.....P...
..P.......
....P2....
...P.P....
.PP..P....
....P....P
PPPP....3.
.P..P.P..P

....P.....
P....1....
.P.....PP.
.PP....PP.
.2.P.P....
..P....P..
.P........
.....P.P..
P.....P...
.3.P.P....

..P..P..P.
..1....P.P
..........
.......2P.
...P....P3
.P...PP..P
.......P.P
..P..P..PP
..P.4P..P.
.......P..

..P...P...
.....P....
PPPP...P..
..P.......
...P......
.......P.1
.P..P....P
P2PP......
.P..P.....
..........

......PP.P
.P1..P.P..
......PP..
P..P....2.
.P.P3.....
....4..P..
.......PP.
..P5......
P.....P...
....PPP..P

¡Buena suerte, y no dejes que los pepinillos se queden allí por mucho tiempo, o se echarán a perder!


Ah, y ¿por qué pepinillos, preguntas?

Por qué no?

Pomo de la puerta
fuente
3
No hay una manera razonable de demostrar que realmente encontró el "resultado óptimo" ya que esto es esencialmente un problema de vendedor ambulante (hombres) y es NP completo.
Wally
@Wally Hmm, ¿lo es? Quizás alguien debería encontrar los pasos mínimos para el caso de prueba proporcionado, y luego todas las respuestas pueden basarse en eso.
Pomo de la puerta
2
El caso de prueba es probablemente lo suficientemente pequeño como para obligar al mínimo a la fuerza bruta, si alguien quisiera hacer eso. Y / o todos los que respondan podrían decir qué obtuvieron para el caso de prueba y usted podría requerir otras respuestas que al menos coincidan con ese mínimo.
Wally
3
¿Pueden los robots pasar el uno al otro? Si no, ¿cuáles son las restricciones de tiempo para interpretar las rutas?
Peter Taylor
1
@Gareth El problema con eso es que los puntajes no se conocerán hasta que se revelen los casos de prueba, y luego los envíos después de eso ya verán los casos de prueba.
Pomo de la puerta

Respuestas:

6

Rubí, 15 + 26 + 17 + 26 + 17 = 101

¡El robot encuentra encurtidos!

Bien, aquí hay una línea de base para que las personas comiencen, usando el siguiente algoritmo súper ingenuo:

  • Cada tic, cada robot actuará en orden numérico, realizando los siguientes pasos:
    • Identifique el encurtido más cercano (distancia de Manhattan) que ningún otro robot está apuntando.
    • Averigua qué cuadrados adyacentes están disponibles para moverte.
    • Elija uno de esos cuadrados, prefiriendo direcciones que lo acerquen al pepinillo seleccionado.

Esto es lo que parece para el caso de prueba # 1:

Ejemplo animado para TC1

Obviamente, esto no es muy bueno, ¡pero es un comienzo!

Código:

Tile = Struct.new(:world, :tile, :x, :y) do
    def passable?
        tile =~ /\.|P/
    end

    def manhattan_to other
        (self.x - other.x).abs + (self.y - other.y).abs
    end

    def directions_to other
        directions = []
        directions << ?U if self.y > other.y
        directions << ?D if self.y < other.y
        directions << ?L if self.x > other.x
        directions << ?R if self.x < other.x
        directions
    end

    def one_step direction
        nx,ny = case direction
            when ?U then [self.x, self.y - 1]
            when ?D then [self.x, self.y + 1]
            when ?L then [self.x - 1, self.y]
            when ?R then [self.x + 1, self.y]
        end

        self.world[nx,ny]
    end

    def move direction
        destination = one_step(direction)
        raise "can't move there" unless destination && destination.passable?

        destination.tile, self.tile = self.tile, ?.
    end
end

class World
    DIRECTIONS = %w(U D L R)

    def initialize s
        @board = s.split.map.with_index { |l,y| l.chars.map.with_index { |c,x| Tile.new(self, c, x, y) }}
        @width = @board[0].length
    end

    def [] x,y
        y >= 0 && x >= 0 && y < @board.size && x < @board[y].size && @board[y][x]
    end

    def robots
        tiles_of_type(/[0-9]/).sort_by { |t| t.tile }
    end

    def pickles
        tiles_of_type ?P
    end

    def tiles_of_type type
        @board.flatten.select { |t| type === t.tile }
    end

    def inspect
        @board.map { |l| l.map { |t| t.tile }*'' }*?\n
    end
end

gets(nil).split("\n\n").each do |input|
    w = World.new(input)
    steps = Hash[w.robots.map { |r| [r.tile, []] }]
    while (pickles_remaining = w.pickles).size > 0
        current_targets = Hash.new(0)

        w.robots.each do |r|
            target_pickle = pickles_remaining.min_by { |p| [current_targets[p], r.manhattan_to(p)] }

            possible_moves = World::DIRECTIONS.select { |d| t = r.one_step(d); t && t.passable? }
            raise "can't move anywhere" if possible_moves.empty?

            direction = (r.directions_to(target_pickle) & possible_moves).first || possible_moves[0]

            current_targets[target_pickle] += 1
            steps[r.tile] << direction
            r.move(direction)
        end
    end

    puts steps.values.map &:join
    p steps.values.map { |v| v.size }.max
end

Toma información sobre STDIN exactamente en el formato del bloque de código del caso de prueba en la pregunta original. Esto es lo que imprime para esos casos de prueba:

DDLLDLLLLULLUUD
LDLRRDDLDLLLLDR
URDDLLLLLULLUUL
15
ULDLDDLDRRRURRURDDDDDDDLLL
UUULDDRDRRRURRURDLDDDDLDLL
ULUURURRDDRRRRUUUDDDDLDLLL
26
URRRDRUDDDDLLLDLL
RUUUDLRRDDDLLLDLL
LDRDDLDDLLLLLLLUU
RUUURDRDDLLLLLUUU
17
DULLUUUUULDLDLLLLLDDRUUUUR
UDLRRRURDDLLLUUUUURDRUUUUD
26
LDDLDUUDDDUDDDDDR
ULUULDDDDDRDRDDDR
LULLDUUDDDRDRDDDR
UUUURDUURRRRDDDDD
LDLLUDDRRRRRRUDRR
17
Paul Prestidge
fuente
1

Python, 16 + 15 + 14 + 20 + 12 = 77

Realmente no tengo experiencia previa en problemas de tipo vendedor ambulante, pero tenía un poco de tiempo libre, así que pensé en intentarlo. Básicamente intenta asignar a cada bot ciertos pepinillos caminando a través de una carrera preliminar donde van por los más cercanos y más alejados de los demás. Luego, fuerza bruta la forma más eficiente para que cada bot recoja sus encurtidos asignados.

Realmente no tengo idea de cuán viable es este método, pero sospecho que no funcionaría bien para tableros más grandes con menos bots (el cuarto tablero a veces lleva más de dos minutos).

Código:

def parse_input(string):
    pickles = []
    size = len(string) - string.count('\n')
    poses = [None] * (size - string.count('.') - string.count('P'))
    for y,line in enumerate(string.strip().split('\n')):
        for x,char in enumerate(line):
            if char == '.':
                continue
            elif char == 'P':
                pickles.append((x,y))
            else:
                poses[int(char)-1] = (x,y)
    return pickles, poses

def move((px,py),(tx,ty)):
    if (px,py) == (tx,ty):
        return (px,py)
    dx = tx-px
    dy = ty-py
    if abs(dx) <= abs(dy):
        if dy < 0:
            return (px,py-1)
        else:
            return (px,py+1)
    else:
        if dx < 0:
            return (px-1,py)
        else:
            return (px+1,py)

def distance(pos, pickle):
    return abs(pos[0]-pickle[0]) + abs(pos[1]-pickle[1])

def calc_closest(pickles,poses,index):
    distances = [[distance(pos,pickle) for pickle in pickles] for pos in poses]
    dist_diffs = []
    for i, pickle_dists in enumerate(distances):
        dist_diffs.append([])
        for j, dist in enumerate(pickle_dists):
            other = [d[j] for d in distances[:i]+distances[i+1:]]
            dist_diffs[-1].append(min(other)-dist)

    sorted = pickles[:]
    sorted.sort(key = lambda ppos: -dist_diffs[index][pickles.index(ppos)])
    return sorted

def find_best(items,level):
    if level == 0:
        best = (None, None)
        for rv, rest in find_best(items[1:],level+1):
            val = distance(items[0],rest[0]) + rv
            if best[0] == None or val < best[0]:
                best = (val, [items[0]] + rest)
        return best

    if len(items) == 1:
        return [(0,items[:])]

    size = len(items)
    bests = []
    for i in range(size):
        best = (None, None)
        for rv, rest in find_best(items[:i]+items[i+1:],level+1):
            val = distance(items[i],rest[0]) + rv
            if best[0] == None or val < best[0]:
                best = (val, [items[i]] + rest)
        if best[0] != None:
            bests.append(best)
    return bests

def find_best_order(pos,pickles):
    if pickles == []:
        return 0,[]
    best = find_best([pos]+pickles,0)
    return best

def walk_path(pos,path):
    history = ''
    while path:
        npos = move(pos, path[0])
        if npos == path[0]:
            path.remove(path[0])

        if npos[0] < pos[0]:
            history += 'L'
        elif npos[0] > pos[0]:
            history += 'R'
        elif npos[1] < pos[1]:
            history += 'U'
        elif npos[1] > pos[1]:
            history += 'D'
        pos = npos
    return history

def find_paths(input_str):
    pickles, poses = parse_input(input_str)                 ## Parse input string and stuff
    orig_pickles = pickles[:]
    orig_poses = poses[:]
    numbots = len(poses)

    to_collect = [[] for i in range(numbots)]               ## Will make a list of the pickles each bot should go after
    waiting = [True] * numbots
    targets = [None] * numbots
    while pickles:
        while True in waiting:                              ## If any bots are waiting for a new target
            index = waiting.index(True)
            closest = calc_closest(pickles,poses,index)     ## Prioritizes next pickle choice based upon how close they are RELATIVE to other bots
            tar = closest[0]

            n = 0
            while tar in targets[:index]+targets[index+1:]:                 ## Don't target the same pickle!
                other_i = (targets[:index]+targets[index+1:]).index(tar)
                dist_s = distance(poses[index],tar)
                dist_o = distance(poses[other_i],tar)
                if dist_s < dist_o:
                    waiting[other_i] = True
                    break

                n += 1
                if len(closest) <= n:
                    waiting[index] = False
                    break
                tar = closest[n]

            targets[index] = tar
            waiting[index] = False      

        for i in range(numbots):                            ## Move everything toward targets  (this means that later target calculations will not be based on the original position)
            npos = move(poses[i], targets[i])
            if npos != poses[i]:
                poses[i] = npos
            if npos in pickles:
                to_collect[i].append(npos)
                pickles.remove(npos)
                for t, target in enumerate(targets):
                    if target == npos:
                        waiting[t] = True               

    paths = []
    sizes = []

    for i,pickle_group in enumerate(to_collect):                    ## Lastly brute force the most efficient way for each bot to collect its allotted pickles
        size,path = find_best_order(orig_poses[i],pickle_group)
        sizes.append(size)
        paths.append(path)
    return max(sizes), [walk_path(orig_poses[i],paths[i]) for i in range(numbots)]

def collect_pickles(boards):
    ## Collect Pickles!
    total = 0
    for i,board in enumerate(boards):
        result = find_paths(board)
        total += result[0]
        print "Board "+str(i)+": ("+ str(result[0]) +")\n"
        for i,h in enumerate(result[1]):
            print '\tBot'+str(i+1)+': '+h
        print

    print "Total Score: " + str(total)

boards = """
P.......1.
..........
P.....P...
..P.......
....P2....
...P.P....
.PP..P....
....P....P
PPPP....3.
.P..P.P..P

....P.....
P....1....
.P.....PP.
.PP....PP.
.2.P.P....
..P....P..
.P........
.....P.P..
P.....P...
.3.P.P....

..P..P..P.
..1....P.P
..........
.......2P.
...P....P3
.P...PP..P
.......P.P
..P..P..PP
..P.4P..P.
.......P..

..P...P...
.....P....
PPPP...P..
..P.......
...P......
.......P.1
.P..P....P
P2PP......
.P..P.....
..........

......PP.P
.P1..P.P..
......PP..
P..P....2.
.P.P3.....
....4..P..
.......PP.
..P5......
P.....P...
....PPP..P
""".split('\n\n')

collect_pickles(boards)

Salida:

Board 0: (16)

    Bot1: DLDLLLLDLLULUU
    Bot2: LDLDLLDDLDRURRDR
    Bot3: URDDLLLULULURU

Board 1: (15)

    Bot1: ULRDRDRRDLDDLUL
    Bot2: DDURURULLUUL
    Bot3: ULRRDRRRURULRR

Board 2: (14)

    Bot1: URRRDDDDDRLLUL
    Bot2: UUURDRDDLD
    Bot3: DDDLDDLUUU
    Bot4: RULLLDUUUL

Board 3: (20)

    Bot1: DLULUUUUULDLLLULDDD
    Bot2: LURDDURRDRUUUULUULLL

Board 4: (12)

    Bot1: LDDLDR
    Bot2: ULUULRRR
    Bot3: LUURURDR
    Bot4: RRRDRDDDR
    Bot5: LLDLRRRDRRRU

Total Score: 77
KSab
fuente