Roboty! Zbierz te pikle!

10

Wydaje mi się, że wpadłem w zalew. Dosłownie Upuściłem kilka pikli na podłogę, a teraz wszystkie są rozrzucone! Musisz mi pomóc zebrać je wszystkie. Och, czy wspominałem, że mam do dyspozycji kilka robotów? (Wszystkie są też rozrzucone po całym mieście; jestem naprawdę kiepski w organizowaniu rzeczy.)

Musisz wziąć wkład w postaci:

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

czyli wielokrotne linie albo ., P(ogórek) lub cyfra (ID robota). (Możesz założyć, że każda linia ma równą długość, .jest uzupełniona.) Możesz wprowadzić te linie jako tablicę, lub slurp ze STDIN, lub odczytać w oddzielonym przecinkami pojedynczym wierszu, lub odczytać plik, lub zrobić cokolwiek byś zrobił lubię brać wkład.

Twój wynik musi mieć postać nlinii, gdzie njest najwyższy identyfikator robota. (Identyfikatory robotów będą zawsze sekwencyjne, zaczynając od 1.) Każda linia będzie zawierać ścieżkę robota, utworzoną z liter L(po lewej), R(po prawej), U(w górę) i D(w dół). Na przykład oto przykładowy wynik dla tej układanki:

LLU
RDR
LRRR
D

Może być również

LLU RDR LRRR D

Lub

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

Lub dowolny format, który chcesz, pod warunkiem, że możesz powiedzieć, jakie powinno być rozwiązanie.

Twoim celem jest znalezienie optymalnej wydajności, która ma najmniejszą liczbę kroków. Ilość kroków jest liczona jako największa liczba kroków ze wszystkich robotów. Na przykład powyższy przykład miał 4 kroki. Pamiętaj, że może istnieć wiele rozwiązań, ale wystarczy tylko jedno z nich.

Punktacja:

  • Twój program zostanie uruchomiony z każdym z 5 (losowo wygenerowanych) przypadków testowych.
  • Musisz dodać kroki z każdego biegu, a to będzie twój wynik.
  • Wygra najniższy łączny łączny wynik.
  • Nie można na stałe kodować tych konkretnych danych wejściowych. Twój kod powinien także działać z innymi wejściami.
  • Roboty mogą się przenikać.
  • Twój program musi być deterministyczny, tj. Taki sam wynik dla każdego uruchomienia. Możesz użyć generatora liczb losowych, o ile jest on rozstawiony i konsekwentnie wytwarza te same liczby na różnych platformach.
  • Twój kod musi zostać uruchomiony w ciągu 3 minut dla każdego wejścia. (Najlepiej znacznie mniej.)
  • W przypadku remisu większość zwycięstw wygra.

Oto przypadki testowe. Zostały wygenerowane losowo za pomocą małego skryptu Ruby, który napisałem.

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

Powodzenia i nie pozwól, aby pikle siedzieli tam zbyt długo, bo inaczej się zepsują!


Aha, a dlaczego pikle, pytasz?

Dlaczego nie?

Klamka
źródło
3
Nie ma żadnego rozsądnego sposobu na wykazanie, że rzeczywiście znalazłeś „optymalną wydajność”, ponieważ jest to zasadniczo problem podróżnego sprzedawcy (mężczyzn) i NP jest kompletny.
Wally
@Wally Hmm, prawda? Być może ktoś powinien znaleźć minimalne kroki dla podanego przypadku testowego, a wtedy wszystkie odpowiedzi mogą być oparte na tym.
Klamka
2
Przypadek testowy jest prawdopodobnie wystarczająco mały, aby brutalnie zminimalizować siłę - jeśli ktoś chciałby to zrobić. I / lub każdy, kto udzieli odpowiedzi, może powiedzieć, co dostał dla przypadku testowego, a Ty możesz wymagać innych odpowiedzi, aby przynajmniej dopasować to minimum.
Wally
3
Czy roboty mogą się przenikać? Jeśli nie, jakie są ograniczenia czasowe interpretacji ścieżek?
Peter Taylor,
1
@Gareth Problem polega na tym, że wyniki nie będą znane, dopóki nie zostaną ujawnione przypadki testowe, a następnie zgłoszenia będą już widoczne.
Klamka

Odpowiedzi:

6

Rubin, 15 + 26 + 17 + 26 + 17 = 101

Robot znajdzie pikle!

Okej, oto podstawa, od której ludzie mogą zacząć, używając następującego naiwnego algorytmu:

  • Każdy tik, każdy robot będzie działał w kolejności numerycznej, wykonując następujące kroki:
    • Zidentyfikuj najbliższą (odległość na Manhattanie) marynatę, na którą nie celują żadne inne roboty.
    • Sprawdź, do których sąsiadujących pól można się przenieść.
    • Wybierz jeden z tych kwadratów, preferując kierunki, które przybliżą go do wybranego ogórka.

Oto jak wygląda przypadek testowy nr 1:

Animowany przykład dla TC1

Oczywiście nie jest to zbyt dobre, ale to początek!

Kod:

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

Pobiera dane wejściowe STDIN w dokładnie formacie bloku kodu przypadku testowego w pierwotnym pytaniu. Oto, co drukuje dla tych przypadków testowych:

DDLLDLLLLULLUUD
LDLRRDDLDLLLLDR
URDDLLLLLULLUUL
15
ULDLDDLDRRRURRURDDDDDDDLLL
UUULDDRDRRRURRURDLDDDDLDLL
ULUURURRDDRRRRUUUDDDDLDLLL
26
URRRDRUDDDDLLLDLL
RUUUDLRRDDDLLLDLL
LDRDDLDDLLLLLLLUU
RUUURDRDDLLLLLUUU
17
DULLUUUUULDLDLLLLLDDRUUUUR
UDLRRRURDDLLLUUUUURDRUUUUD
26
LDDLDUUDDDUDDDDDR
ULUULDDDDDRDRDDDR
LULLDUUDDDRDRDDDR
UUUURDUURRRRDDDDD
LDLLUDDRRRRRRUDRR
17
Paul Prestidge
źródło
1

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

Tak naprawdę nie mam żadnego wcześniejszego doświadczenia z problemami typu sprzedawca w podróży, ale miałem trochę czasu, więc pomyślałem, że spróbuję. Zasadniczo próbuje on przydzielić każdemu botowi niektóre pikle, przechodząc go przez wstępny bieg, w którym idą do tych najbliższych i najdalszych od innych. Następnie brutalnie wymusza najskuteczniejszy sposób zbierania przydzielonych pikli dla każdego bota.

Naprawdę nie mam pojęcia, jak opłacalna jest ta metoda, ale podejrzewam, że nie sprawdziłaby się w przypadku większych kart z mniejszą liczbą botów (czwarta tablica już czasami zajmuje ponad dwie minuty).

Kod:

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)

Wynik:

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
źródło