Rô bốt! Thu thập những dưa chua!


10

Tôi dường như đã nhận được một chút của một dưa chua. Nghĩa đen Tôi đánh rơi một đống dưa chua trên sàn và bây giờ tất cả chúng đều nằm rải rác! Tôi cần bạn giúp tôi thu thập tất cả. Ồ, tôi đã đề cập đến việc tôi có một loạt các robot theo lệnh của tôi chưa? (Chúng cũng nằm rải rác khắp nơi; Tôi rất tệ trong việc sắp xếp mọi thứ.)

Bạn phải lấy đầu vào dưới dạng:

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

tức là, nhiều dòng của một trong hai ., P(dưa muối) hoặc một chữ số (ID của robot). (Bạn có thể giả sử rằng mỗi dòng có độ dài bằng nhau, được đệm bằng ..) Bạn có thể nhập các dòng này dưới dạng một mảng hoặc đọc từ STDIN hoặc đọc trong một dòng đơn được phân tách bằng dấu phẩy hoặc đọc tệp hoặc làm bất cứ điều gì bạn muốn muốn lấy đầu vào.

Đầu ra của bạn phải ở dạng nđường kẻ, nơi ncó ID robot cao nhất. (ID Robot sẽ luôn được sắp xếp theo thứ tự bắt đầu từ 1.) Mỗi ​​dòng sẽ chứa đường dẫn của robot, được hình thành từ các chữ cái L(trái), R(phải), U(lên) và D(xuống). Ví dụ: đây là một ví dụ đầu ra cho câu đố đó:

LLU
RDR
LRRR
D

Nó cũng có thể là

LLU RDR LRRR D

Hoặc là

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

Hoặc bất kỳ định dạng nào bạn muốn, miễn là bạn có thể cho biết giải pháp được cho là gì.

Mục tiêu của bạn là tìm ra đầu ra tối ưu, là đầu ra có ít bước nhất. Số lượng các bước được tính là số lượng bước lớn nhất từ ​​tất cả các robot. Ví dụ, ví dụ trên có 4 bước. Lưu ý rằng có thể có nhiều giải pháp, nhưng bạn chỉ cần xuất một giải pháp.

Ghi điểm:

  • Chương trình của bạn sẽ được chạy với mỗi trong số 5 trường hợp thử nghiệm (được tạo ngẫu nhiên).
  • Bạn phải thêm các bước từ mỗi lần chạy và đó sẽ là điểm số của bạn.
  • Tổng số thấp nhất, điểm tích lũy sẽ giành chiến thắng.
  • Bạn có thể không cứng mã cho các đầu vào cụ thể này. Mã của bạn cũng sẽ làm việc cho bất kỳ đầu vào khác.
  • Robot có thể đi qua nhau.
  • Chương trình của bạn phải có tính xác định, tức là cùng một đầu ra cho mỗi lần chạy. Bạn có thể sử dụng một trình tạo số ngẫu nhiên, miễn là nó được tạo mầm và luôn tạo ra cùng một nền tảng số.
  • Mã của bạn phải chạy trong vòng 3 phút cho mỗi đầu vào. (Ưu tiên ít hơn nhiều.)
  • Trong trường hợp hòa, hầu hết các upvote sẽ giành chiến thắng.

Dưới đây là các trường hợp thử nghiệm. Chúng được tạo ngẫu nhiên với một tập lệnh Ruby nhỏ mà tôi đã viết lên.

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

Chúc may mắn và đừng để dưa chua ngồi đó quá lâu, nếu không chúng sẽ hư hỏng!


Oh, và tại sao dưa chua, bạn hỏi?

Tại sao không?


3
Không có cách nào hợp lý để cho thấy rằng bạn thực sự tìm thấy "đầu ra tối ưu" vì đây thực chất là vấn đề của nhân viên bán hàng du lịch và NP đã hoàn tất.
Wally

@Wally Hmm, nó là? Có lẽ ai đó nên tìm các bước tối thiểu cho trường hợp thử nghiệm được cung cấp, và sau đó tất cả các câu trả lời có thể dựa trên điều đó.
Doorknob

2
Trường hợp thử nghiệm có thể đủ nhỏ để vũ phu tối thiểu - nếu ai đó muốn làm điều đó. Và / hoặc tất cả những người trả lời có thể cho biết những gì họ nhận được cho trường hợp thử nghiệm và bạn có thể yêu cầu các câu trả lời khác ít nhất khớp với mức tối thiểu đó.
Wally

3
Robot có thể đi qua nhau không? Nếu không, các hạn chế về thời gian trong việc diễn giải các đường dẫn là gì?
Peter Taylor

1
@Gareth Vấn đề với điều đó là điểm số sẽ không được biết cho đến khi các testcase được tiết lộ, và sau đó đệ trình sau đó sẽ thấy các testcase.
Doorknob

Câu trả lời:


6

Hồng ngọc, 15 + 26 + 17 + 26 + 17 = 101

Robot tìm dưa chua!

Được rồi, đây là một đường cơ sở để bắt đầu mọi người, sử dụng thuật toán siêu ngây thơ sau đây:

  • Mỗi đánh dấu, mỗi robot sẽ hành động theo thứ tự số, thực hiện các bước sau:
    • Xác định dưa chua gần nhất (khoảng cách Manhattan) mà không có robot nào khác đang nhắm mục tiêu.
    • Chỉ ra các ô vuông liền kề có sẵn để di chuyển đến.
    • Chọn một trong những hình vuông đó, ưu tiên các hướng di chuyển nó gần hơn với dưa chua đã chọn.

Đây là giao diện của Test Case # 1:

Ví dụ hoạt hình cho TC1

Rõ ràng điều này không tốt lắm nhưng đó là một sự khởi đầu!

Mã số:

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

Đưa đầu vào vào STDIN theo định dạng chính xác của khối mã trường hợp thử nghiệm trong câu hỏi ban đầu. Đây là những gì nó in cho các trường hợp thử nghiệm:

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

1

Con trăn, 16 + 15 + 14 + 20 + 12 = 77

Tôi thực sự không có bất kỳ kinh nghiệm nào trước đây đối với các vấn đề về nhân viên bán hàng du lịch nhưng tôi đã có một chút thời gian trên tay nên tôi nghĩ rằng tôi sẽ thử. Về cơ bản, nó cố gắng phân bổ cho mỗi bot một số dưa chua bằng cách đi bộ qua đường chạy sơ bộ nơi chúng đi cho những con gần chúng nhất và xa nhất so với những con khác. Sau đó, brute buộc các cách hiệu quả nhất cho mỗi bot để thu thập dưa chua được phân bổ của nó.

Tôi thực sự không biết phương pháp này khả thi đến mức nào, nhưng tôi nghi ngờ nó sẽ không hoạt động tốt đối với các bảng lớn hơn với ít bot hơn (bảng thứ tư đôi khi mất hơn hai phút).

Mã số:

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)

Đầu ra:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.