Роботи! Збирайте ці соління!


10

Я, здається, потрапив трохи на соління. Буквально. Я скинув на підлогу купу солінь і тепер вони всі розкидані! Мені потрібно, щоб ти допомогла мені зібрати їх усіх. О, я згадав, що у мене на купі є купа роботів? (Вони також усі розкидані по всьому місцю; мені дуже погано впорядковувати речі.)

Ви повинні взяти дані у вигляді:

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

тобто, кілька рядків або ., P(розсіл), або цифра (ID робота). (Ви можете припустити, що кожен рядок має однакову довжину, закреслений ..) Ви можете вводити ці рядки у вигляді масиву, або прив'язувати до STDIN, чи читати в окремому рядку, розділеному комами, чи читати файл, або робити все, що ви хочете як би взяти вклад.

Вихід повинен бути у вигляді nрядків, де nнайвищий ідентифікатор робота. (Ідентифікатори роботів завжди будуть послідовними, починаючи з 1.) Кожен рядок буде містити шлях робота, сформований з літер L(ліворуч), R(праворуч), U(вгору) та D(вниз). Наприклад, ось приклад для цієї головоломки:

LLU
RDR
LRRR
D

Так само може бути

LLU RDR LRRR D

Або

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

Або будь-який формат, який ви хотіли б, поки ви зможете сказати, яке рішення має бути.

Ваша мета - знайти оптимальний вихід, який є найменшим кроком. Кількість кроків вважається найбільшою кількістю кроків від усіх роботів. Наприклад, вищевказаний приклад мав 4 етапи. Зауважте, що може бути кілька рішень, але вам потрібно вивести лише одне.

Оцінка:

  • Ваша програма буде запускатися з кожного з 5 (випадково генерованих) тестових випадків.
  • Ви повинні додати кроки з кожного запуску, і це буде ваш рахунок.
  • Виграє найнижчий, сукупний бал.
  • Ви не можете ввести жорсткий код для цих конкретних входів. Ваш код також повинен працювати для будь-якого іншого вводу.
  • Роботи можуть проходити один через одного.
  • Ваша програма повинна бути детермінованою, тобто однаковим результатом для кожного запуску. Ви можете використовувати генератор випадкових чисел до тих пір, поки він засіяний і послідовно створює однакові номери крос-платформи.
  • Ваш код повинен працювати протягом 3 хвилин для кожного з входів. (Переважно набагато менше.)
  • У разі нічиєї виграє більшість гравців.

Ось тестові випадки. Вони були генеровані випадковим чином за допомогою невеликого сценарію Ruby, який я написав.

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

Удачі, і не дозволяйте соління сидіти там занадто довго, інакше вони зіпсуються!


О, а навіщо соління, запитаєте ви?

Чому ні?


3
Немає розумного способу показати, що ви насправді знайшли "оптимальний вихід", оскільки це, по суті, проблема продавця (чоловіків), який подорожує, і є NP повним.
Уоллі

@Wally Хм, це? Можливо, хтось повинен знайти мінімальні кроки для наданого тестового випадку, і тоді всі відповіді можуть базуватися на цьому.
Дверна ручка

2
Тестовий випадок, ймовірно, досить малий, щоб змусити мінімум - якщо хтось хотів це зробити. І / або кожен, хто відповість, може сказати, що вони отримали для тестового випадку, і ви можете вимагати інших відповідей, щоб принаймні відповідати цьому мінімуму.
Уоллі

3
Чи можуть роботи проходити один через одного? Якщо ні, то які часові обмеження щодо інтерпретації шляхів?
Пітер Тейлор

1
@Gareth Проблема в тому, що результати не будуть відомі, поки не будуть виявлені тестові шкали, а потім подання після цього вже побачить тестові шкали.
Дверна ручка

Відповіді:


6

Рубі, 15 + 26 + 17 + 26 + 17 = 101

Робот знаходить соління!

Гаразд, ось базовий напрямок для того, щоб розпочати роботу людей, використовуючи такий супернаївний алгоритм:

  • Кожна галочка, кожен робот діятимуть у числовому порядку, виконуючи наступні дії:
    • Визначте найближчий (Манхеттенський) соління, на яке не націлені інші роботи.
    • З’ясуйте, до яких сусідніх квадратів можна перейти.
    • Виберіть один із цих квадратів, віддаючи перевагу напрямкам, які наближають його до вибраного соління.

Ось як виглядає тест №1:

Анімований приклад для TC1

Очевидно, це не дуже добре, але це початок!

Код:

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

Вводить дані про STDIN в точно такому форматі коду-блоку тестового випадку в оригінальному запитанні. Ось що він друкує для цих тестових випадків:

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

1

Пітона, 16 + 15 + 14 + 20 + 12 = 77

Я не маю жодного попереднього досвіду проблем з типом продавця подорожей, але у мене було небагато часу, тому я думав, що зроблю це. По суті, це намагається виділити кожному боту певні соління, вигулюючи його, хоч попередній пробіг, куди вони йдуть за тими, хто їм найближчий і віддалений від інших. Тоді грубими силами є найефективнішим способом для кожного бота збирати свої виділені соління.

Я дійсно не маю поняття, наскільки життєздатним є цей метод, але я підозрюю, що він би не працював добре для більших плат з меншою кількістю ботів (четверта дошка іноді займає більше двох хвилин).

Код:

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)

Вихід:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.