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: Accepted)

import java.util.Arrays;
import java.util.Scanner;

public class Main {

    private static int INF = Integer.MAX_VALUE;

    public static void main(String[] args) {
        // 読み込んでフィールドを作る
        final Scanner sc = new Scanner(System.in);

        while (true) {
            final int x = sc.nextInt();
            final int y = sc.nextInt();

            if (x == 0 && y == 0) {
                break;
            }

            int[][] pachimon = new int[5][1000];
            int[] pachimonLength = new int[] {0, 0, 0, 0, 0};
            int s = 0;
            int g = 0;

            for (int fy = 0; fy < y; fy++) {
                final char[] in = sc.next().toCharArray();

                for (int fx = 0; fx < x; fx++) {
                    final char c = in[fx];

                    switch (c) {
                    case 'S':
                        s = pos(fx, fy);
                        break;
                    case 'G':
                        g = pos(fx, fy);
                        break;
                    case '.':
                        break;
                    default:
                        final int num = Character.getNumericValue(c) - 1;
                        pachimon[num][pachimonLength[num]] = pos(fx, fy);
                        pachimonLength[num]++;
                        break;
                    }
                }
            }

            int start = -1;
            int dist = INF;

            // 属性ごとにループ
            for (int i = 0; i < 5; i++) {
                // DPの初期化 DP[属性][同じ属性内の連番]
                int[][] dp = new int[5][1000];
                for (int h = 0; h < 5; h++) {
                    Arrays.fill(dp[h], INF);
                }

                // 最初に選んだパチモンから次に捕まえられる属性番号
                int first = (i + 1) % 5;

                for (int j = 0; j < pachimonLength[first]; j++) {
                    // s→e1を計算
                    dp[first][j] = dist(s, pachimon[first][j]);
                }

                // s->1->2->3->4->g
                // なので、ループは間の-> * 3分まわす
                for (int e = 0; e < 3; e++) {
                    // ex->e(x+1)
                    int now = (first + e) % 5;
                    int next = (now + 1) % 5;
                    for (int j = 0; j < pachimonLength[now]; j++) {
                        for (int k = 0; k < pachimonLength[next]; k++) {
                            dp[next][k] = Math.min(
                                    dp[next][k],
                                    add(dp[now][j],
                                            dist(pachimon[now][j],
                                                    pachimon[next][k])));
                        }
                    }
                }

                int last = (first + 3) % 5;
                for (int j = 0; j < pachimonLength[last]; j++) {
                    // e4->g
                    int d = add(dp[last][j], dist(pachimon[last][j], g));

                    if (d < dist) {
                        dist = d;
                        start = i;
                    }

                }

            }
            if (dist == INF) {
                System.out.println("NA");
            } else {
                System.out.println((start + 1) + " " + dist);
            }

        }
    }

    private static final int pos(final int x, final int y) {
        return (x << 11) + y;
    }

    private static final int dist(final int from, final int to) {
        return abs((from >> 11) - (to >> 11))
                + abs((from & 2047) - (to & 2047));
    }

    private static final int abs(final int a) {
        final int t = a >> 31;
        return (a ^ t) - t;// これも
    }

    private static int add(int x, int y) {
        if (x == INF || y == INF) {
            return INF;
        }
        return x + y;
    }
}