Problem:


 Acrophobia

 C: 高所恐怖症

高杉やよいは，超売れっ子アイドルである．
そんな彼女には，1つ苦手なものがある．
高いところだ・・・．彼女は，極度の高所恐怖症なのである．
彼女は今回，プロデューサーの不手際により，バラエティ番組で次のようなチャレンジに挑戦することになってしまった．

今回のロケは，とある忍者屋敷のとある一室で行われる．
この部屋の床には，正方形のタイルが敷き詰められており，いくつかのタイルが抜け落ちて穴になっている．
この穴から落ちると数メートル下の池にまっさかさまである．
やよいのチャレンジ内容は，この部屋の指定された場所からスタートし，部屋の中に置かれた全ての巻物を回収し，ゴール地点まで持っていくことである．

やよいは，部屋の中を以下のルールに従って移動できる．

 あるタイルから隣のタイルへ移動するとき，上下左右のタイルにのみ移動することができる．

 穴に近くない場合，隣のタイルへ移動するためには，1秒だけ時間がかかる．

 穴に近づいた場合，やよいは怖がってしまい図C-1のように移動時間がかかる．例えば，図中の矢印のように，[2][2]から[1][2]へ移動するためには，3秒かかる．

 複数の穴が近くにある場合，最も近くにある穴に対して怖がる．

 巻物を取るためにかかる時間は，0と仮定してよい．

図C-1: 穴の近くの移動時間

やよいは，なるべく早くこのチャレンジを終わらせたいと考えている．
あなたの仕事は，このチャレンジを終了するための最短時間を求めるプログラムを書き，やよいを助けてあげることである．

 Input

忍者屋敷の床情報が与えられる．

まず，1行目に部屋の大きさを表す，WとHがスペース区切りで入力される(2 <= W, H <= 100)．
続くH行の各行には，W文字の文字列が入力される．
床情報を表す文字には，以下の種類がある．

 '.' : 歩ける床

 '#' : 穴

 'S' : やよいが開始時に立っている場所

 'G' : やよいが辿りつくべき場所

 'M' : 巻物が置いてある場所

'S'と'G'と'M'は，歩ける床である．
'S'と'G'はそれぞれ，部屋の中にちょうど1個存在する．全ての巻物が揃っていない状態でゴールを通過しても構わない．
'M'は，部屋の中に最小で1個，最大で5個存在する．

 Output

全ての巻物を集めて，スタートからゴールへたどり着くための最短タイムを1行に出力せよ．入力データには，必ずそのようなルートがあると仮定してよい．

行の最後には，改行を出力すること．

 Sample Input 1

3 4
S.M
...
...
M.G

 Sample Output 1

9

 Sample Input 2

4 4
S..M
....
...#
#..G

 Sample Output 2

18

 Sample Input 3

11 7
M.........M
...........
...#.S.#...
...........
...#.G.#...
...........
M.........M

 Sample Output 3

62



Sample input:
3 4
S.M
...
...
M.G


Sample output:
9

Submission status: Accepted

Solution code:
from itertools import permutations
from collections import deque

w, h = map(int, input().split())
mp = ["X" * (w + 4)] * 2 + ["XX" + input() + "XX" for _ in range(h)] + ["X" * (w + 4)] * 2
m_lst = []
holes = []
for y in range(2, h + 2):
  for x in range(2, w + 2):
    if mp[y][x] == "S":
      sx, sy = x, y
    if mp[y][x] == "G":
      gx, gy = x, y
    if mp[y][x] == "M":
      m_lst.append((x, y))
    if mp[y][x] == "#":
      holes.append((x, y))

move_costs = [[1] * (w + 4) for _ in range(h + 4)]
for x, y in holes:
  for ny in range(y - 2, y + 3):
    for nx in range(x - 2, x + 3):
      move_costs[ny][nx] = max(2, move_costs[ny][nx])

  for ny in range(y - 1, y + 2):
    for nx in range(x - 1, x + 2):
      move_costs[ny][nx] = 3

def shortest(x1, y1):
  INF = 10 ** 20
  vec = ((1, 0), (0, -1), (-1, 0), (0, 1))
  costs = [[INF] * (w + 4) for _ in range(h + 4)]
  costs[y1][x1] = 0
  que = deque()
  que.append((0, x1, y1))
  while que:
    score, x, y = que.popleft()
    new_score = score + move_costs[y][x]
    for dx, dy in vec:
      nx, ny = x + dx, y + dy
      if mp[ny][nx] in {"S", "G", ".", "M"} and new_score < costs[ny][nx]:
        costs[ny][nx] = new_score
        que.append((new_score, nx, ny))
  return costs

edges = []
for x, y in m_lst:
  costs = shortest(x, y)
  edge = []
  for x2, y2 in m_lst:
    edge.append(costs[y2][x2])
  edge.append(costs[gy][gx])
  edges.append(edge)

start_cost = shortest(sx, sy)
goal_cost = shortest(gx, gy)
ans = 10 ** 20
for t in permutations(range(len(edges)), len(edges)):
  lst = list(t)
  score = 0
  for i in range(len(edges) - 1):
    score += edges[lst[i]][lst[i + 1]]
  xs, ys = m_lst[lst[0]]
  xf, yf = m_lst[lst[-1]]
  score += start_cost[ys][xs]
  score += edges[lst[-1]][-1]
  ans = min(ans, score)

print(ans)