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 C# (judge verdict: Accepted)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace _0215
{

    public class Creature
    {
        public int ID { get; set; }
        public int I { get; set; }
        public int J { get; set; }
        public Creature(int id, int i, int j) { ID = id; I = i; J = j; }
        public static int CalcDistance(Creature a, Creature b)
        {
            return Math.Abs(a.I - b.I) + Math.Abs(a.J - b.J);
        }
    }

    public class Program
    {
        private const int TYPES = 5;
        private const int MAXCNT_IN_TYPE = 1000;

        private static Creature start;
        private static Creature goal;
        private static List<Creature>[] creatures;
        private static int[] distances;

        public static void Main(string[] args)
        {
            while (true)
            {
                int[] vs = RIntAr();
                if (vs.Sum() == 0) break;

                Init(vs);
                if (creatures.Count(x => x.Count() == 0) > 1)
                {
                    Console.WriteLine("NA"); //2種類以上のクリーチャーがフィールドにいない場合は達成不可
                }
                else
                {
                    var tup = CalcDistance();
                    Console.WriteLine(tup.Item1 == int.MaxValue ? "NA" : "{0} {1}", tup.Item1 + 1, tup.Item2);
                }
            }
        }

        private static void Init(int[] vs)
        {
            creatures = new List<Creature>[TYPES];
            for (int i = 0; i < creatures.Count(); i++) creatures[i] = new List<Creature>(MAXCNT_IN_TYPE);

            int id = 1;
            for (int i = 0; i < vs[1]; i++)
            {
                string line = RSt();
                for (int j = 0; j < vs[0]; j++)
                {
                    switch (line[j])
                    {
                        case 'S':
                            start = new Creature(0, i, j);
                            break;
                        case 'G':
                            goal = new Creature(TYPES * MAXCNT_IN_TYPE + 1, i, j);
                            break;
                        case '.':
                            break;
                        default:
                            int type = int.Parse(line[j].ToString()) - 1;
                            creatures[type].Add(new Creature(id, i, j));
                            id++;
                            break;
                    }
                }
            }
        }

        private static Tuple<int, int> CalcDistance()
        {
            int minIdx = int.MaxValue;
            int minD = int.MaxValue;
            for (int i = 0; i < TYPES; i++)
            {
                distances = new int[TYPES * MAXCNT_IN_TYPE + 2];
                for (int j = 0; j < distances.Length; j++) distances[j] = int.MaxValue;
                Dijkstra(i);
                if (distances[goal.ID] < minD)
                {
                    minIdx = i;
                    minD = distances[goal.ID];
                }
            }
            return new Tuple<int, int>(minIdx, minD);
        }

        /// <summary>
        /// 始点からの距離をセットする（ダイクストラ法）
        /// </summary>
        /// <param name="s">始めに所持している属性</param>
        class Candidate : IComparable<Candidate>
        {
            public int I { get; set; } //何本目の辺か
            public Creature V { get; set; }
            public int Distance { get; set; }
            public Candidate(int i, Creature v, int d) { I = i; V = v; Distance = d; }
            public int CompareTo(Candidate other) { return -1 * Distance.CompareTo(other.Distance); }
        }
        public static void Dijkstra(int s)
        {
            distances[start.ID] = 0;
            PriorityQueue<Candidate> pq = new PriorityQueue<Candidate>();
            pq.Enqueue(new Candidate(1, start, 0));

            while (pq.Count > 0)
            {
                Candidate c = pq.Dequeue();
                int i = c.I;
                Creature v = c.V;
                int d = c.Distance;

                if (distances[v.ID] < d) continue;
                if (v.ID == goal.ID) break;

                List<Creature> nexts = new List<Creature>();
                if (i < TYPES) nexts = creatures[(s + i) % TYPES];
                else nexts.Add(goal);

                foreach (var nextV in nexts)
                {
                    int tmpdist = distances[v.ID] + Creature.CalcDistance(v, nextV);
                    if (distances[nextV.ID] > tmpdist)
                    {
                        distances[nextV.ID] = tmpdist;
                        pq.Enqueue(new Candidate(i + 1, nextV, distances[nextV.ID]));
                    }
                }
            }
        }

        static string RSt() { return Console.ReadLine(); }
        static int RInt() { return int.Parse(Console.ReadLine().Trim()); }
        static long RLong() { return long.Parse(Console.ReadLine().Trim()); }
        static double RDouble() { return double.Parse(Console.ReadLine()); }
        static string[] RStAr(char sep = ' ') { return Console.ReadLine().Trim().Split(sep); }
        static int[] RIntAr(char sep = ' ') { return Array.ConvertAll(Console.ReadLine().Trim().Split(sep), e => int.Parse(e)); }
        static long[] RLongAr(char sep = ' ') { return Array.ConvertAll(Console.ReadLine().Trim().Split(sep), e => long.Parse(e)); }
        static double[] RDoubleAr(char sep = ' ') { return Array.ConvertAll(Console.ReadLine().Trim().Split(sep), e => double.Parse(e)); }
        static string WAr<T>(IEnumerable<T> array, string sep = " ") { return string.Join(sep, array.Select(x => x.ToString()).ToArray()); }

    }

    public class PriorityQueue<T> where T : IComparable<T>

    {

        private List<T> Buffer { get; set; }

        public int Count { get { return Buffer.Count; } }

        public PriorityQueue() { Buffer = new List<T>(); }
        public PriorityQueue(int capacity) { Buffer = new List<T>(capacity); }


        /// <summary>
        /// ヒープ化されている配列リストに新しい要素を追加する。
        /// </summary>
        public void Enqueue(T item)
        {
            int n = Buffer.Count;
            Buffer.Add(item);

            while (n != 0)
            {
                int i = (n - 1) / 2;
                if (Buffer[n].CompareTo(Buffer[i]) > 0)
                {
                    T tmp = Buffer[n]; Buffer[n] = Buffer[i]; Buffer[i] = tmp;
                }
                n = i;
            }
        }

        /// <summary>
        /// ヒープから最大値を取り出し、削除する。
        /// </summary>
        public T Dequeue()
        {
            T ret = Buffer[0];
            int n = Buffer.Count - 1;
            Buffer[0] = Buffer[n];
            Buffer.RemoveAt(n);

            for (int i = 0, j; (j = 2 * i + 1) < n;)
            {
                if ((j != n - 1) && (Buffer[j].CompareTo(Buffer[j + 1]) < 0))
                    j++;
                if (Buffer[i].CompareTo(Buffer[j]) < 0)
                {
                    T tmp = Buffer[j]; Buffer[j] = Buffer[i]; Buffer[i] = tmp;
                }
                i = j;
            }
            return ret;
        }

        /// <summary>
        /// ヒープから最大値を参照する。
        /// </summary>
        public T Peek()
        {
            if (Count == 0) throw new InvalidOperationException();
            return this.Buffer[0];
        }
    }

}