Problem:
パチモンクリーチャー

某国で大人気のゲーム、パチモンクリーチャーが日本でリメイクされて発売されました。 ゲームが大好きなあなたは、 このゲームを何度もプレイするうちにどうしたら最速でクリアできるのか考えるようになりました。しかし、いくら考えても最速の攻略方法がわからなかったあなたは、どれだけ早くゲームをクリアできるかを求めるプログラムを作成することにしました。

ゲームの詳細は以下の通りです。

パチモンクリーチャー(以下、パチクリ)という生物が多く存在する世界がゲームの舞台です。各パチクリは、火属性、氷属性、木属性、土属性、水属性の 5 種類の属性のいずれか１つの属性を持ちます。ゲームの主人公は、ゲーム開始時に好きな属性のパチクリ一匹を冒険のパートナーとして選びます。そのパチクリと共にゴールを目指し、ゴールにいるライバルを倒してパチクリマスターになることがゲームの目的です。

しかし、ライバルを倒すためには全属性のパチクリがいないと勝てないので、途中で全属性のパチクリを捕まえなければなりません。パチクリを捕まえるには属性がカギとなります。火属性のパチクリは氷属性のパチクリを捕まえることができ、同様に、氷属性は木属性、木属性は土属性、土属性は水属性、水属性は火属性を捕まえることができます。属性の関連は以下の図のようになります。

以下の図はゲームが行われるマップの一例を表しています。

主人公はパチクリを一匹もってスタート地点である「S」から出発し、一マスずつ移動しながらゴール地点である「G」を目指します。その途中で、最初に持っているパチクリ以外の 4 つの属性のパチクリを捕まえ、ゴール地点であるマス目に移動するとゲーム終了となります。

主人公は、今いるマス目から、辺を共有する隣のマス目に移動することができ、それを一回の移動と数えます。主人公がパチクリのいるマスに移動した場合、そのパチクリを捕まえられる属性のパチクリを持っていればそのパチクリを捕まえたことになります。そのマスにいるパチクリを捕まえられるかの可否にかかわらず、すべてのマスに何度でも移動することができます。

マップの大きさ(横方向の列数、縦方向の行数)とマップの初期状態を入力とし、初めに選んだパチクリの属性と、それ以外の 4 つの属性のパチクリを捕まえるのにかかる、スタート地点からゴール地点に至る最小移動数を出力するプログラムを作成してください。

Input

複数のデータセットの並びが入力として与えられます。入力の終りはゼロふたつの行で示されます。各データセットは以下の形式で与えられます。

W H
c11c12...c1W
c21c22...c2W
:
cH1cH2...cHW

1 行目にマップの横方向の列数 W と縦方向の行数 H (2 ≤ W, H ≤ 1000) が与えられます。続く H 行にマップの i 行目の情報が与えられます。入力されるマップには各マスの状態が与えられます。 「S」は主人公のスタート地点を、 「G」はゴール地点を、「1」「2」「3」「4」「5」はそこにいるパチクリの属性を( 1:火属性、 2:氷属性、 3:木属性、 4:土属性、 5:水属性 をそれぞれ表します)、 「.(ピリオド) 」は何もないマスをそれぞれ表します。
各属性のパチクリの数はそれぞれ 0 以上 1000 以下とします。

データセットの数は140 を超えません。また、データセットの 80 % について、W, H は100 を超えません。

Output

入力データセットごとに、最初に選んだパチクリの属性と最小移動数を１行に出力します。なお、どのように初めのパチクリを選んでも、どのような経路で移動してもそれら 4 つの属性のパチクリを捕まえることができない場合は NA と出力してください。 また、最小移動数が同じになるような最初のパチクリの選び方が複数ある場合は、属性の数字が小さいものを出力してください。

Sample Input

6 6
S.1..4
3..5..
..4.1.
4....5
.2.32.
5.1..G
3 2
...
S.G
0 0

Output for the Sample Input

2 10
NA

Solution in JAVA (judge verdict: Memory Limit Exceeded)

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;

class Point {
    public static int nextId = 0;
    public int id;
    public int x;
    public int y;

    public Point(int x, int y) {
        this.id = nextId++;
        this.x = x;
        this.y = y;
    }

    public int distance(Point p) {
        return Math.abs(this.x - p.x) + Math.abs(this.y - p.y);
    }
}

public class Main {
    public static BufferedReader br =
            new BufferedReader(new InputStreamReader(System.in));

    public static void main(String args[]) throws IOException {            
        List<List<Point> > pointListByType =
                new ArrayList<List<Point> >();

        for(int i = 0; i < 5; i++) {
            pointListByType.add(new ArrayList<Point>());
        }
        
        while(true) {
            String mapSizeStr = br.readLine();
            if("0 0".equals(mapSizeStr)) break;

            String[] splittedStr = mapSizeStr.split(" ");
            int x = Integer.parseInt(splittedStr[0]);
            int y = Integer.parseInt(splittedStr[1]);
            
            for(int i = 0; i < 5; i++) {
                pointListByType.get(i).clear();
            }

            int pointCount = 0;
            Point start = null;
            Point goal = null;
            Point.nextId = 0;

            for(int r = 0; r < y; r++){
                String line = br.readLine();
                for(int c = 0; c < x; c++) {
                    char pointLiteral = line.charAt(c);
                    switch(pointLiteral) {
                    case 'S':
                        start = new Point(c, r);
                        pointCount++;
                        break;
                    case 'G':
                        goal = new Point(c, r);
                        pointCount++;
                        break;
                    case '1':case '2':case '3':case '4':case '5':
                        int type = (int)pointLiteral - (int)'1';
                        pointListByType.get(type).add(new Point(c, r));
                        pointCount++;
                        break;
                    }
                }
            }
            
            
            //到達不可能判定
            //1匹もパチモンがいない属性が2つ以上あれば、到達不能
            int zeroTypeCount = 0;
            for(int i = 0 ; i < 5; i++) {
                if(pointListByType.get(i).size() == 0) {
                    if(++zeroTypeCount >= 2) {
                        break;
                    }
                }
            }
            if(zeroTypeCount >= 2) {
                System.out.println("NA");
                continue;
            }

            int minLenType = -1;
            int minLen = Integer.MAX_VALUE;
            for (int firstType = 0; firstType < 5; firstType++) {
                
                //最初の一匹以外のいずれかの属性のパチモンがいない場合到達不能
                if(
                    firstType == 0 && (
                        pointListByType.get(1).size() == 0 ||
                        pointListByType.get(2).size() == 0 ||
                        pointListByType.get(3).size() == 0 ||
                        pointListByType.get(4).size() == 0
                    ) ||
                    firstType == 1 && (
                        pointListByType.get(0).size() == 0 ||
                        pointListByType.get(2).size() == 0 ||
                        pointListByType.get(3).size() == 0 ||
                        pointListByType.get(4).size() == 0
                    ) ||
                    firstType == 2 && (
                        pointListByType.get(0).size() == 0 ||
                        pointListByType.get(1).size() == 0 ||
                        pointListByType.get(3).size() == 0 ||
                        pointListByType.get(4).size() == 0
                    ) ||
                    firstType == 3 && (
                        pointListByType.get(0).size() == 0 ||
                        pointListByType.get(1).size() == 0 ||
                        pointListByType.get(2).size() == 0 ||
                        pointListByType.get(4).size() == 0
                    ) ||
                    firstType == 4 && (
                        pointListByType.get(0).size() == 0 ||
                        pointListByType.get(1).size() == 0 ||
                        pointListByType.get(2).size() == 0 ||
                        pointListByType.get(3).size() == 0
                    )
                ) {
                    continue;
                }

                //make graph
                int [] graph = new int[pointCount * pointCount];
                Arrays.fill(graph, Integer.MAX_VALUE);

                for(int i = 0; i < pointListByType.size(); i++) {
                    List<Point> tempPointList = pointListByType.get(i);
                    for(int j = 0; j < tempPointList.size(); j++) {
                        Point tempPoint = tempPointList.get(j);
                        // from Start to evry point expect Goal
                        if(i == (firstType + 1) % 5) {
                            setGraph(graph, start, tempPoint, pointCount);
                        }
                        // to goal from every point expect Start
                        if(i == (firstType + 4) % 5) {
                            setGraph(graph, tempPoint, goal, pointCount);
                        }
                    }
                } 
                
                // from any Point to Point
                List<Point> firePointList = pointListByType.get(0);
                List<Point> icePointList = pointListByType.get(1);
                List<Point> treePointList = pointListByType.get(2);
                List<Point> earthPointList = pointListByType.get(3);
                List<Point> waterPointList = pointListByType.get(4);
                //  fire to ice
                for (int i = 0; i < firePointList.size(); i++) {
                    for (int j = 0; j < icePointList.size(); j++) {
                        setGraph(
                            graph,
                            firePointList.get(i),
                            icePointList.get(j),
                            pointCount
                        );
                    }
                }
                //  ice to tree
                for (int i = 0; i < icePointList.size(); i++) {
                    for (int j = 0; j < treePointList.size(); j++) {
                        setGraph(
                            graph,
                            icePointList.get(i),
                            treePointList.get(j),
                            pointCount
                        );
                    }
                }
                //  tree to earth
                for (int i = 0; i < treePointList.size(); i++) {
                    for (int j = 0; j < earthPointList.size(); j++) {
                        setGraph(
                            graph,
                            treePointList.get(i),
                            earthPointList.get(j),
                            pointCount
                        );
                    }
                }
                //  earth to water
                for (int i = 0; i < earthPointList.size(); i++) {
                    for (int j = 0; j < waterPointList.size(); j++) {
                        setGraph(
                            graph,
                            earthPointList.get(i),
                            waterPointList.get(j),
                            pointCount
                        );
                    }
                }
                //  water to fire
                for (int i = 0; i < waterPointList.size(); i++) {
                    for (int j = 0; j < firePointList.size(); j++) {
                        setGraph(
                            graph,
                            waterPointList.get(i),
                            firePointList.get(j),
                            pointCount
                        );
                    }
                }

                //search shortest path
                int shortestPathLen = 
                        searchShortestPath(graph, start, goal, pointCount);

                if(minLen > shortestPathLen) {
                    minLen = shortestPathLen;
                    minLenType = firstType;
                }
            }

            if(minLen == Integer.MAX_VALUE) {
                System.out.println("NA");
            } else {
                System.out.println((minLenType + 1) + " " + minLen);
            }
        }
    }

    private static void setGraph(
                    int[] graph,
                    Point fromPoint,
                    Point toPoint,
                    int pointCount
    ) {
        graph[fromPoint.id * pointCount + toPoint.id] =
            fromPoint.distance(toPoint);
    }

    private static int searchShortestPath(
        int[] graph, Point start, Point goal, int pointCount
    ) {
        int[] d = new int[pointCount];
        Arrays.fill(d, Integer.MAX_VALUE);
        d[start.id] = 0;
        int[] prevId = new int[pointCount];
        Arrays.fill(prevId, -1);
        int searchedPointCount = 0;
        boolean[] fixed = new boolean[pointCount];
        Arrays.fill(fixed, false);
        
        int searchingId = 0;
        while(searchedPointCount < pointCount - 1) {
            int minD = Integer.MAX_VALUE;
            for(int i = 0; i < pointCount; i++) {
                if(!fixed[i] && minD > d[i]) {
                    fixed[i] = true;
                    searchedPointCount++;
                    minD = d[i];
                    searchingId = i;
                    break;
                }
            }
            for(int i = 0; i < pointCount; i++) {
                if(fixed[i]) continue;
                int distance = graph[searchingId * pointCount + i];
                if(distance == Integer.MAX_VALUE) continue;
                if(d[i] > d[searchingId] + distance) {
                    d[i] = d[searchingId] + distance;
                    prevId[i] = searchingId;
                }
            }
        }

        //探索完了
        return d[goal.id];
    }
}