Robot! Colleziona questi sottaceti!


10

Mi sembra di essermi messo un po 'in agitazione. Letteralmente. Ho lasciato cadere un mucchio di sottaceti sul pavimento e ora sono tutti sparpagliati! Ho bisogno che tu mi aiuti a raccoglierli tutti. Oh, ho detto che ho un sacco di robot al mio comando? (Sono anche tutti sparsi dappertutto; sono davvero pessimo nell'organizzare le cose.)

È necessario prendere input sotto forma di:

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

vale a dire, più righe di ., P(sottaceto) o una cifra (ID robot). (Si può presumere che ogni riga sia della stessa lunghezza, riempita con ..) È possibile inserire queste righe come un array, o bere da STDIN, oppure leggere in una riga separata da virgola, o leggere un file o fare qualunque cosa tu voglia piace prendere l'input.

L'output deve essere sotto forma di nlinee, dove nè l'ID robot più alto. (Gli ID robot saranno sempre sequenziali a partire da 1.) Ogni riga conterrà il percorso del robot, formato dalle lettere L(sinistra), R(destra), U(su) e D(giù). Ad esempio, ecco un esempio di output per quel puzzle:

LLU
RDR
LRRR
D

Può anche essere

LLU RDR LRRR D

O

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

O qualsiasi formato desideri, purché tu possa dire quale dovrebbe essere la soluzione.

Il tuo obiettivo è trovare l'output ottimale, che è quello che ha il minor numero di passaggi. La quantità di passaggi viene conteggiata come la più grande quantità di passaggi da tutti i robot. Ad esempio, l'esempio sopra aveva 4 passaggi. Nota che potrebbero esserci più soluzioni, ma devi solo produrne una.

punteggio:

  • Il programma verrà eseguito con ciascuno dei 5 casi di test (generati casualmente).
  • Devi aggiungere i passaggi di ciascuna corsa e quello sarà il tuo punteggio.
  • Vincerà il punteggio cumulativo totale più basso.
  • Non è possibile codificare per questi input specifici. Il tuo codice dovrebbe funzionare anche per qualsiasi altro input.
  • I robot possono attraversarsi.
  • Il programma deve essere deterministico, ovvero lo stesso output per ogni esecuzione. È possibile utilizzare un generatore di numeri casuali, purché sia ​​seminato e produca costantemente gli stessi numeri multipiattaforma.
  • Il codice deve essere eseguito entro 3 minuti per ciascuno degli input. (Preferibilmente molto meno.)
  • In caso di pareggio, vincerà la maggior parte dei voti.

Ecco i casi di test. Sono stati generati casualmente con una piccola sceneggiatura di Ruby che ho scritto.

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

Buona fortuna e non lasciare che i sottaceti rimangano lì troppo a lungo, altrimenti rovinano!


Oh, e perché i sottaceti, chiedi?

Perchè no?


3
Non esiste un modo ragionevole per dimostrare che hai effettivamente trovato "l'output ottimale" poiché si tratta essenzialmente di un problema di commesso viaggiatore (uomini) ed è NP completo.
Wally,

@Wally Hmm, lo è? Forse qualcuno dovrebbe trovare i passaggi minimi per il caso di test fornito, e quindi tutte le risposte possono essere basate su quello.
Maniglia della porta

2
Il caso di test è probabilmente abbastanza piccolo da rendere minima la forza bruta, se qualcuno voleva farlo. E / o tutti coloro che rispondono potrebbero dire che cosa hanno ottenuto per il test case e potresti aver bisogno di altre risposte almeno corrispondenti a quel minimo.
Wally,

3
I robot possono attraversarsi l'un l'altro? In caso contrario, quali sono le restrizioni temporali sull'interpretazione dei percorsi?
Peter Taylor

1
@Gareth Il problema è che i punteggi non saranno conosciuti fino a quando non saranno rivelati i testcase, e quindi dopo l'invio vedranno già i testcase.
Maniglia della porta

Risposte:


6

Rubino, 15 + 26 + 17 + 26 + 17 = 101

Il robot trova sottaceti!

Ok, ecco una linea di base per iniziare le persone, usando il seguente algoritmo super-ingenuo:

  • Ogni segno di spunta, ogni robot agirà in ordine numerico, procedendo come segue:
    • Identifica il sottaceto più vicino (distanza di Manhattan) che nessun altro robot sta prendendo di mira.
    • Scopri quali quadrati adiacenti sono disponibili per spostarti.
    • Scegli uno di quei quadrati, preferendo le direzioni che lo avvicinano al sottaceto selezionato.

Ecco come appare per il caso di test n. 1:

Esempio animato per TC1

Ovviamente questo non è molto buono ma è un inizio!

Codice:

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

Prende input su STDIN esattamente nel formato del blocco di codice del caso di test nella domanda originale. Ecco cosa stampa per quei casi di test:

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

1

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

In realtà non ho alcuna esperienza precedente con i problemi del tipo di commesso viaggiatore, ma avevo un po 'di tempo a disposizione, quindi ho pensato di provarlo. In pratica tenta di assegnare a ciascun robot determinati sottaceti percorrendolo attraverso una corsa preliminare in cui vanno per quelli più vicini a loro e più lontani dagli altri. Quindi forza bruta il modo più efficiente per ogni bot di raccogliere i sottaceti assegnati.

Non ho davvero idea di quanto sia fattibile questo metodo, ma sospetto che non funzionerebbe bene con le schede più grandi con meno bot (la quarta scheda a volte impiega già più di due minuti).

Codice:

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)

Produzione:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.