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

// 標準入力
const INF = 1 << 30;
const ELEM_X = 11;
const ELEM_Y = 2047;

var fs = require('fs'),
length = fs.fstatSync(process.stdin.fd).size,
buffer = new Buffer(length),
bytesRead = fs.readSync(process.stdin.fd, buffer, 0, length, 0),
input = buffer.toString('utf8', 0, bytesRead).split('\n');
main();

// ここから
function main(){
  var readLines = 0;
  while(true){
    var fieldInfo = input[readLines].split(' ');
    var width = fieldInfo[0] - 0;//toInt
    var height = fieldInfo[1] - 0;//toInt
     
    // 0 0が入力された
    if(!width && !height){
      break;
    }
     
    var start;
    var goal;
    var elem = [[],[],[],[],[]]; // 場にいるパチモン[属性][属性内連番]
    var c;
    var rowData;
    var elemCache;
    var y;
    var x;
     
    for(y = height; y--;){// reverse loop
      readLines++;//1行進める
      rowData = input[readLines];
      for(x = width; x--;){// reverse loop
        c = rowData[x];
         
        if(c == '.'){
          // ドットが一番多いはずなので、先にひっかけてしまう
        }else if(c == '1' || c == '2' || c == '3' || c == '4' || c == '5'){
          // 次に多いはずの属性を潰す
          elemCache = elem[c - 1];
          elemCache[elemCache.length] = pos(x, y);
        }else if(c == 'S'){
          start = pos(x, y);
        }else{
          goal = pos(x, y);
        }
         
      }
    }
     
    // TypedArrayを採用して高速化
    for(var elemInit = 5; elemInit--;){
      elem[elemInit] = new Int32Array(elem[elemInit]);
    }
     
    // ブロックスコープが無いから変数宣言をまとめる
    var bestElement = -1;
    var currentDisance;
    var nearestDistance = INF;
    var dp; // 距離[属性][同じ属性内の連番]
    var i; // loop index
    var j;
    var e;
    var len;
    var dpCacheNow; // cache of dp[i] for loop
    var dpCacheNowi; // cache of dp[now][i]
    var dpCacheNext; // cache of dp[i + 1] for loop
    var elemCacheNow;
    var elemCacheNowi; // cache of elem[now][i]
    var elemCacheNext;
    var now;
    var nowLength;
    var next;
    var nextLength;
     
    // 最初に選ぶ属性ごとにループ
    for (var selectElem = 0; selectElem < 5; selectElem++) {
      dp = new Array(5); // 距離[属性][同じ属性内の連番]
       
      for(i = 5; i--;){// reverse loop
        len  = elem[i].length;
        dpCache = dp[i] = new Int32Array(len); // ここもTypedArray
        while(len--){// reverse loop
          dpCache[len] = INF;
        }
      }
       
      now = (selectElem + 1) % 5;
      nowLength = elem[now].length;
      dpCacheNow = dp[now];
      elemCacheNow= elem[now];
       
      for (i = 0; i < nowLength; i++) {
        // s→e1を計算
        dpCacheNow[i] = dist(start, elemCacheNow[i]);
      }
       
      // s->1->2->3->4->g
      // なので、ループは間の-> * 3分まわす
      for (e = 0; e < 3; e++) {
        // e(x)->e(x+1)
        next = (now + 1) % 5;
        dpCacheNext = dp[next];
        elemCacheNext = elem[next];
        nextLength = elemCacheNext.length;
        for (i = 0; i < nowLength; i++) {
          dpCacheNowi = dpCacheNow[i];
          elemCacheNowi = elemCacheNow[i];
           
          if(dpCacheNowi >= nearestDistance){
            // 既に決まっている最短距離を超えたら絶対に候補ではない
            continue;
          }
           
          for (j = 0; j < nextLength; j++) {
            dpCacheNext[j] = min(dpCacheNext[j], dpCacheNowi + dist(elemCacheNowi, elemCacheNext[j])); // メモと新しい経路の最短
          }
        }
         
        now = next;
        dpCacheNow = dpCacheNext;
        elemCacheNow = elemCacheNext;
        nowLength = nextLength;
      }
       
      for (i = 0; i < nextLength; i++) {
        // e4->g
        currentDistance = dpCacheNext[i] + dist(elemCacheNext[i], goal);
         
        if (currentDistance < nearestDistance) {
          nearestDistance = currentDistance;
          bestElement = selectElem;
        }
         
      }
       
    }
     
    if (nearestDistance == INF) {
      console.log("NA");
    } else {
      console.log((bestElement + 1) + " " + nearestDistance);
    }
     
    readLines++;
  }
   
}
 
function dist(from, to){
  // x座標とy座標の切り出し(11ビット右シフトでxを、2047(下位10ビット1)とのAND演算でyを取り出す)
  return abs((from >> ELEM_X) - (to >> ELEM_X)) + abs((from & ELEM_Y) - (to & ELEM_Y));
}
 
function min(a, b){
  // ビット演算で高速化
  return b + ((a - b) & ((a - b) >> 31));// 変数宣言よりもこっちの方が高速
}
 
function pos(a, b){
  // オブジェクトのインスタンス作るよりもプリミティブの方が早い
  // 2^10 = 1024なので、下位10ビットでy座標を、上位ビットでx座標を表す
  return (a << ELEM_X) + b;
}
 
function abs(a){
  // ビット演算で高速化
  return (a ^ (a >> 31)) - (a >> 31);// これも
}