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

Solution in JAVA (judge verdict: Accepted)

import java.util.*;

public class Main {
	int w, h, min;
	char[][] cmap;
	int[][] imap, ncost;
	ArrayList<int[]> list;
	
	int[] a = new int[]{0,0,1,-1};
	int[] b = new int[]{1,-1,0,0};
	
	void check(int[] perm){
		int[] perms = new int[list.size()];
		for(int i=0;i<list.size()-2;i++){
			perms[i+1] = perm[i]+1;
		}
		perms[list.size()-1] = list.size()-1;

		int sum = 0;
		for(int i=0;i<list.size()-1;i++){
			int cc = ncost[perms[i]][perms[i+1]];
			if(cc==0){
				calcCost(perms[i], perms[i+1]);
				cc = ncost[perms[i]][perms[i+1]];
			}
			sum += cc;
		}
		min = Math.min(min, sum);
	}
	
	void makeperm(int k, int[] perm, boolean[] flag){
		if(k==list.size()-2){
			check(perm);
		}else{
			for(int i=0;i<list.size()-2;i++){
				if(flag[i]==true) continue;
				perm[k] = i;
				flag[i] = true;
				makeperm(k+1, perm, flag);
				flag[i] = false;
			}
		}
	}
	
	
	void calcCost(int sid, int gid){
		ArrayDeque<Integer> y = new ArrayDeque<Integer>();
		ArrayDeque<Integer> x = new ArrayDeque<Integer>();
		int sy = list.get(sid)[0];
		int sx = list.get(sid)[1];
		int gy = list.get(gid)[0];
		int gx = list.get(gid)[1];
		y.offer(sy);
		x.offer(sx);
		
		int[][] cost = new int[h][w];
		for(int i=0;i<h;i++){
			for(int j=0;j<w;j++){
				cost[i][j] = Integer.MAX_VALUE;
			}
		}
		cost[sy][sx] = 0;
		while(!y.isEmpty()){
			int ty = y.poll();
			int tx = x.poll();
			for(int i=0;i<4;i++){
				int ny = ty + a[i];
				int nx = tx + b[i];
				if(ny>=0 && ny<h && nx>=0 && nx<w && cmap[ny][nx]!='#'){
					if(cost[ny][nx] > cost[ty][tx]+imap[ty][tx]){
						cost[ny][nx] = cost[ty][tx]+imap[ty][tx];
						y.offer(ny);
						x.offer(nx);
					}
				}
			}
		}
		ncost[sid][gid] = cost[gy][gx];
	}
	
	void mark(int y, int x){
		int ty = Math.max(0, y-2);
		int ey = Math.min(h-1, y+2);
		int tx = Math.max(0, x-2);
		int ex = Math.min(w-1, x+2);
		
		for(int i=ty;i<=ey;i++){
			for(int j=tx;j<=ex;j++){
				imap[i][j] = Math.max(imap[i][j], 2);
			}
		}
		
		ty = Math.max(0, y-1);
		ey = Math.min(h-1, y+1);
		tx = Math.max(0, x-1);
		ex = Math.min(w-1, x+1);
		for(int i=ty;i<=ey;i++){
			for(int j=tx;j<=ex;j++){
				imap[i][j] = Math.max(imap[i][j], 3);
			}
		}
		
		imap[y][x] = 4;
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		
		w = sc.nextInt();
		h = sc.nextInt();
		cmap = new char[h][w];
		imap = new int[h][w];
		list = new ArrayList<int[]>();
		int sy = -1;
		int sx = -1;
		int gy = -1;
		int gx = -1;
		for(int i=0;i<h;i++){
			String s = sc.next();
			for(int j=0;j<w;j++){
				char c = s.charAt(j);
				cmap[i][j] = c;
				if(c=='S'){
					sy = i;
					sx = j;
				}else if(c=='G'){
					gy = i;
					gx = j;
				}else if(c=='M'){
					list.add(new int[]{i, j});
				}else if(c=='#'){
					mark(i, j);
				}
			}
		}
		list.add(0, new int[]{sy,sx});
		list.add(new int[]{gy,gx});
		for(int i=0;i<h;i++){
			for(int j=0;j<w;j++){
				if(imap[i][j]==0) imap[i][j] = 1;
			}
		}
		ncost = new int[list.size()][list.size()];
		min = Integer.MAX_VALUE;
		
		makeperm(0, new int[list.size()-2], new boolean[list.size()-2]);
		
		System.out.println(min);
	}
	
	public static void main(String[] args) {
		new Main().run();
	}	
}