【AtCoder】ABC317 – E問題について解説【Python

abc317-e プログラミング

そまちょブログのそまちょ(@somachob)です。

この記事は、AtCoder Beginner Contest 317 の E問題についての解説です。

E – Avoid Eye Contact

問題へのリンク

スタートからゴールまでの最短距離を求める問題です。

最短距離は、幅優先探索を使うことで求めることができます。

幅優先探索について、解説した記事もあるので参考にして下さい。

スタートからゴールまでの最短距離を求める

まずは、スタートからゴールまでの最短距離を求めるコードです。

人の視線は考慮できていませんが、これで最短距離を求めることができます。

# collections モジュールから deque クラスをインポートする
from collections import deque

# 入力
H, W = map(int, input().split())
A = [list(input()) for _ in range(H)]

# スタート地点とゴール地点の探索
for y in range(H):
    for x in range(W):
        # スタート地点のとき
        if A[y][x] == 'S':
            sy = y
            sx = x

        # ゴール地点のとき
        if A[y][x] == 'G':
            gy = y
            gx = x


# キューの宣言
queue = deque()
# すべてのマスを -1(未探索)で初期化
distance = [[-1] * W for _ in range(H)]


# 探索用の変数
dx = [-1, 0, 1, 0]
dy = [0, -1, 0, 1]


# キューに追加しない条件にあたるか判定する関数
def is_skip(next_y, next_x):
    # 移動できるマスが盤面外のとき
    if (next_y < 0 or H <= next_y) or (next_x < 0 or W <= next_x):
        return True

    # 移動できるマスが探索済みのとき
    if distance[next_y][next_x] != -1:
         return True

    # 移動できるマスが壁のとき
    if A[next_y][next_x] == '#':
         return True

    return False


# 幅優先探索
def bfs(sy, sx):
    # スタート地点をキューに追加する
    queue.append([sy, sx])
    # スタート地点の距離を0にする
    distance[sy][sx] = 0
    
    # キューが空になるまで繰り返す
    while queue:
        # キューの先頭の要素を取り出す
        y, x = queue.popleft()

        # 先頭の要素から上下左右への処理
        for i in range(4):
            # 現在地点から移動できるマス
            next_y = y + dy[i]
            next_x = x + dx[i]

            # 移動できるマスをキューに追加しないとき
            if is_skip(next_y, next_x):
                continue
                
            # 移動できるマスをキューに追加する
            queue.append([next_y, next_x])
            # 移動できるマスの距離を現在のマスの距離+1にする
            distance[next_y][next_x] = distance[y][x] + 1  


# スタート地点から幅優先探索
bfs(sy, sx)

# 答えの出力
print(distance[gy][gx])

人の視線を考慮する

幅優先探索で最短距離を求めることができました。

あとは、人の視線を探索するフィールドに落とし込むことができればいいです。

入力例1の説明の、次の図を作ることができれば、幅優先探索するだけになります。

abc317-e-field
E – Avoid Eye Contact 入力例1

入力として与えられる A について、視線を反映させて上図のようにしていきます。

視線を反映させる処理については、未実装ですが次のようなコードでよさそうです。

for y in range(H):
    for x in range(W):
        # 右向きの人
        if A[y][x] == '>':
            # 視線をフィールドに反映させる処理

        # 下向きの人
        if A[y][x] == 'v':
            # 視線をフィールドに反映させる処理

        # 左向きの人
        if A[y][x] == '<':
            # 視線をフィールドに反映させる処理

        # 上向きの人
        if A[y][x] == '^':
            # 視線をフィールドに反映させる処理

視線を反映させる処理については関数に切り出して実装します。

向いている方向に伸ばすための変数 dy と dx を使うことで、各方向に向いている人について対応できるようにしています。

# 視線をフィールドに反映させる処理の関数
def update_field(y, x, dy, dx):
    # 障害物や別の人にあたるまで繰り返す
    while True:
        # 向いている方向に1マス伸ばす
        y += dy
        x += dx

        # 盤面外のとき
        if (y < 0 or H <= y) or (x < 0 or W <= x):
            break

        # 障害物や別の人にあたったとき
        if A[y][x] == '#' or A[y][x] in '>v<^':
            break

        # フィールドに視線(!)を反映
        A[y][x] = '!'

視線を反映させるコードについてのみをまとめると次のようなコードになります。

# 入力
H, W = map(int, input().split())
A = [list(input()) for _ in range(H)]

# 視線をフィールドに反映させる処理の関数
def update_field(y, x, dy, dx):
    # 障害物や別の人にあたるまで繰り返す
    while True:
        # 向いている方向に1マス伸ばす
        y += dy
        x += dx

        # 盤面外のとき
        if (y < 0 or H <= y) or (x < 0 or W <= x):
            break

        # 障害物や別の人にあたったとき
        if A[y][x] == '#' or A[y][x] in '>v<^':
            break

        # フィールドに視線(!)を反映
        A[y][x] = '!'


for y in range(H):
    for x in range(W):
        # 右向きの人
        if A[y][x] == '>':
            update_field(y, x, 0, 1)

        # 下向きの人
        if A[y][x] == 'v':
            update_field(y, x, 1, 0)

        # 左向きの人
        if A[y][x] == '<':
            update_field(y, x, 0, -1)

        # 上向きの人
        if A[y][x] == '^':
            update_field(y, x, -1, 0)

# 確認用の出力
print(A)

入力例1について、上のコード実行してみると次のような出力が得られ、図とも一致していることがわかります。

# 見やすいように出力結果を加工しています
[
    ['.', '.', '.', '.', 'S', 'v', '.'], 
    ['.', '>', '!', '!', '!', '!', '!'], 
    ['.', '.', '.', '.', '.', '!', '.'], 
    ['>', '!', '!', '<', '.', '#', '<'], 
    ['^', 'G', '.', '.', '.', '.', '>']
]
abc317-e-field

AC コード

人の視線を反映させるコードと幅優先探索のコードをまとめた AC できるコードです。

幅優先探索のスキップする条件に「視線に入るとき」、「人のとき」を追加しています。

    # 移動できるマスが視線に入るとき
    if A[next_y][next_x] == '!':
         return True

    # 移動できるマスが人のとき
    if A[next_y][next_x] in '>v<^':
         return True
# collections モジュールから deque クラスをインポートする
from collections import deque

# 入力
H, W = map(int, input().split())
A = [list(input()) for _ in range(H)]

# 視線をフィールドに反映させる処理の関数
def update_field(y, x, dy, dx):
    # 障害物や別の人にあたるまで繰り返す
    while True:
        # 向いている方向に1マス伸ばす
        y += dy
        x += dx

        # 盤面外のとき
        if (y < 0 or H <= y) or (x < 0 or W <= x):
            break

        # 障害物や別の人にあたったとき
        if A[y][x] == '#' or A[y][x] in '>v<^':
            break

        # フィールドに視線(!)を反映
        A[y][x] = '!'


for y in range(H):
    for x in range(W):
        # スタート地点のとき
        if A[y][x] == 'S':
            sy = y
            sx = x

        # ゴール地点のとき
        if A[y][x] == 'G':
            gy = y
            gx = x

        # 右向きの人
        if A[y][x] == '>':
            update_field(y, x, 0, 1)

        # 下向きの人
        if A[y][x] == 'v':
            update_field(y, x, 1, 0)

        # 左向きの人
        if A[y][x] == '<':
            update_field(y, x, 0, -1)

        # 上向きの人
        if A[y][x] == '^':
            update_field(y, x, -1, 0)


# キューの宣言
queue = deque()
# すべてのマスを -1(未探索)で初期化
distance = [[-1] * W for _ in range(H)]


# 探索用の変数
dx = [-1, 0, 1, 0]
dy = [0, -1, 0, 1]


# キューに追加しない条件にあたるか判定する関数
def is_skip(next_y, next_x):
    # 移動できるマスが盤面外のとき
    if (next_y < 0 or H <= next_y) or (next_x < 0 or W <= next_x):
        return True

    # 移動できるマスが探索済みのとき
    if distance[next_y][next_x] != -1:
         return True

    # 移動できるマスが壁のとき
    if A[next_y][next_x] == '#':
         return True

    # 移動できるマスが視線に入るとき
    if A[next_y][next_x] == '!':
         return True

    # 移動できるマスが人のとき
    if A[next_y][next_x] in '>v<^':
         return True

    return False


# 幅優先探索
def bfs(sy, sx):
    # スタート地点をキューに追加する
    queue.append([sy, sx])
    # スタート地点の距離を0にする
    distance[sy][sx] = 0
    
    # キューが空になるまで繰り返す
    while queue:
        # キューの先頭の要素を取り出す
        y, x = queue.popleft()

        # 先頭の要素から上下左右への処理
        for i in range(4):
            # 現在地点から移動できるマス
            next_y = y + dy[i]
            next_x = x + dx[i]

            # 移動できるマスをキューに追加しないとき
            if is_skip(next_y, next_x):
                continue
                
            # 移動できるマスをキューに追加する
            queue.append([next_y, next_x])
            # 移動できるマスの距離を現在のマスの距離+1にする
            distance[next_y][next_x] = distance[y][x] + 1  


# スタート地点から幅優先探索
bfs(sy, sx)

# 答えの出力
print(distance[gy][gx])

参考

タイトルとURLをコピーしました