ロボット!これらの漬物を集めてください!


10

少し漬け物になっているようです。文字通り。たくさんの漬物を床に落としました、そして今、それらはすべて散らばっています!それらすべてを集めるのを手伝ってほしい。ああ、私は自分の命令でたくさんのロボットを持っていると言ったのですか?(また、それらはすべてあちこちに散在しています。私は物事を整理するのが本当に苦手です)

次の形式で入力する必要があります。

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

すなわち、いずれかの複数の行.P(漬物)、または数字(ロボットのID)。(各行は同じ長さで、が埋め込まれて.いると想定できます。)これらの行を配列として入力するか、STDINから丸呑みするか、コンマ区切りの単一行で読み取るか、ファイルを読み取るか、または何でもできます入力を受け取りたい。

出力は行の形式である必要があります。nここnで、は最大のロボットIDです。(ロボットIDは常に1から始まります。)各行には、L(左)、R(右)、U(上)、D(下)の文字で構成されるロボットのパスが含まれます。たとえば、そのパズルの出力例は次のとおりです。

LLU
RDR
LRRR
D

それもすることができます

LLU RDR LRRR D

または

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

または、ソリューションが何であるかを理解できる限り、任意の形式を使用できます。

あなたの目標は、最小のステップを持つ最適な出力を見つけることです。歩数は、すべてのロボットの最大歩数としてカウントされます。たとえば、上記の例には4つのステップがあります。複数のソリューションがある場合がありますが、出力する必要があるのは1つだけであることに注意してください。

得点:

  • プログラムは、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完全であるため、「最適な出力」を実際に見つけたことを示す妥当な方法はありません。
ウォーリー

@ウォーリーうーん、そうですか?おそらく、誰かが提供されたテストケースの最小手順を見つけて、すべての回答がそれに基づいている可能性があります。
ドアノブ

2
テストケースは、最小値を総当たりにするのに十分なほど小さいでしょう-誰かがそうしたいのなら。または、回答者全員がテストケースで得たものを伝えることができ、少なくともその最小値に一致するように他の回答を要求することができます。
ウォーリー

3
ロボット同士が通り抜けることはできますか?そうでない場合、パスを解釈する際のタイミング制限は何ですか?
Peter Taylor

1
@Garethその問題は、テストケースが明らかになるまでスコアがわからないことであり、その後の提出では、テストケースがすでに表示されます。
ドアノブ

回答:


6

Ruby、15 + 26 + 17 + 26 + 17 = 101

ロボットが漬物を見つけます!

さて、ここに、次の超ナイーブなアルゴリズムを使用して、人々を始めるためのベースラインがあります:

  • 各ティック、各ロボットは番号順に動作し、次の手順を実行します。
    • 他のロボットがターゲットにしていない最も近い(マンハッタン距離)ピクルを特定します。
    • 移動可能な隣接する正方形を見つけ出します。
    • それらの正方形の1つを選択し、選択した漬物に近づける方向を優先します。

テストケース#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

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

私は出張セールスマンタイプの問題を経験したことがありませんが、少し時間があったので、試してみようと思いました。基本的には、各ボットに特定のピクルスを割り当てて、それらに最も近いものと最も遠いものに行くための予備的な実行を行って、ピクルスを割り当てます。次に、各ボットが割り当てられたピクルスを収集する最も効率的な方法を総当たりします。

この方法がどれほど実行可能かは本当にわかりませんが、ボットが少ない大きなボードではうまく機能しないと思います(4番目のボードはすでに2分以上かかることもあります)。

コード:

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.