Problem:
会津の埋蔵金

会津には古くから伝わる埋蔵金の伝説があります。あなたは、遂に埋蔵金が埋まっている場所を突き止めました。埋蔵金が埋まっている深さ、掘り進める地層の状態が分かっているので、綿密に計画を立てれば最小費用で埋蔵金まで到達することができます。そこであなたは、地層の状態を読み取って埋蔵金の埋まっている深さまで最小費用で到達するルートを算出するプログラムを作成することにしました。

地層の状態は 2 次元格子状に配置されたセルで表わされ、各セルの位置は座標 (x,y) で表されます。左上を (1,1) とし、x 座標は右に行くにつれて大きくなり、y 座標は下に深くなるにつれて大きくなるものとします。あなたは y 座標の一番小さいセルのうち一つを選んでそこから掘り始め、y 座標の一番大きいセルの一つまで掘り進めます。地層には以下の 2 種類のセルがあります：

土の詰まったセル。掘るのに、セルごとに決められた費用がかかる。

酸素のたまったセル。掘る必要はなく、セルごとに決まった量の酸素を補給できる。一度酸素を補給したセルの酸素はなくなり、再度の補給はできない。また、このセルに辿りついたら必ず酸素の補給をしなければならない。

あるセルから掘ることができるのは左右と下方向のセルのみです。
一度掘ったセルを左右に移動することはできますが、上に移動することはできません。

発掘にあたっては、酸素ボンベを携帯しなければなりません。酸素ボンベの残量が 0 になった瞬間、移動も発掘も酸素の補給もできなくなります。残量はセルを移動するたびに 1 減ります。酸素ボンベの残量が 0 で埋蔵金の埋まっている深さまで到達しても、到達したとみなされません。また、酸素のたまったセルでは酸素を補給することができますが、容量を超えた分は廃棄されます。

地層のサイズ 、発掘費用、酸素ボンベの容量、初期状態で持っている酸素の量、地層の情報を入力とし、一番深いセルまでたどりつくための最小費用を出力するプログラムを作成してください。ただし、最小費用が発掘費用を超えてしまう場合や、どのように掘り進めても埋蔵金にたどりつけない場合は“NA”と出力してください。

Input

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

W H
f m o
c1,1 c2,1 ... cW,1
c1,2 c2,2 ... cW,2
...
c1,H c2,H ... cW,H

１行目に地層の横方向のサイズ W, 縦方向のサイズ H (3 ≤ W, H ≤ 10) が与えられる。 ２行目にあなたの発掘費用を表す整数 f (1 ≤ f ≤ 10000)、酸素ボンベの容量を表す整数 m (3 ≤ m ≤ 50)、初期状態で持っている酸素の量を表す整数 o (o ≤ m) が与えられる。

続く H 行に地層の情報 ci,j が与えられる。ci,j は、座標 (i, j) に対するセルの情報を表し、以下の形式で与えられる：

負の値の場合、土の詰まったセルで、値は費用を表す

正の値の場合、酸素のたまったセルで、値は酸素の量を表す

ただし、酸素のたまったセルは 50 箇所以内である。

データセットの数は50 を超えない。

Output

データセットごとに、最少費用または NA を1行に出力する。

Sample Input

3 3
100 10 10
-100 -20 -100
-100 -20 -100
-100 -20 -100
3 3
100 10 10
-100 -20 -100
-100 -20 -20
-100 -60 -20
3 3
100 10 3
-100 -20 -100
-20 -20 -20
-20 -100 -20
3 3
100 3 3
-100 -20 -30
-100 -20 2
-100 -20 -20
4 5
1500 5 4
-10 -380 -250 -250
-90 2 -80 8
-250 -130 -330 -120
-120 -40 -50 -20
-250 -10 -20 -150
0 0

Output for the Sample Input

60
80
NA
50
390

Solution in C# (judge verdict: Memory Limit Exceeded)

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

namespace _0234
{
    class Program
    {
        class State : IComparable<State>
        {
            public Point P { get; set; }
            public int Cost { get; set; }
            public HashSet<Point> Path { get; set; }
            public State(Point p, int cost, HashSet<Point> path)
            {
                P = p;
                Cost = cost;
                Path = new HashSet<Point>();
                if (path != null)
                {
                    foreach (var item in path)
                    {
                        Path.Add(item);
                    }
                } 
                Path.Add(p);
            }

            public int CompareTo(State other)
            {
                return -1 * (this.Cost - other.Cost);
            }
        }

        class Point : IEqualityComparer<Point>
        {
            public int I { get; set; }
            public int J { get; set; }
            public int RestOxy { get; set; }
            public Point(int i, int j, int restOxy) { I = i; J = j; RestOxy = restOxy; }

            public bool Equals(Point x, Point y)
            {
                if (ReferenceEquals(x, y)) return true;
                return x.I == y.I && x.J == x.J && x.RestOxy <= y.RestOxy;
            }

            public int GetHashCode(Point obj)
            {
                int hashI = obj.I.GetHashCode();
                int hashJ = obj.J.GetHashCode();
                int hashRestOxy = obj.RestOxy.GetHashCode();

                return hashI ^ hashJ ^ hashRestOxy;
            }
        }

        static readonly int[] dj = new int[] { 1, -1, 0 };
        static readonly int[] di = new int[] { 0, 0, 1 };
        static int maxCost;
        static int maxOxy;
        static int startOxy;
        static int[,] map;
        static int[,,] closed;

        static void Main(string[] args)
        {
            while (true)
            {
                int[] wh = RArInt();
                if (wh.Sum() == 0) break;
                Init(wh);
                Console.WriteLine(CalcMinCost());
            }
        }

        private static void Init(int[] wh)
        {
            int[] vs = RArInt();
            maxCost = vs[0]; maxOxy = vs[1]; startOxy = vs[2];

            map = new int[wh[1], wh[0]];
            for (int i = 0; i < map.GetLength(0); i++)
            {
                vs = RArInt();
                for (int j = 0; j < map.GetLength(1); j++)
                {
                    map[i, j] = vs[j];
                }
            }

            closed = new int[wh[1], wh[0], maxOxy];
            Buffer.BlockCopy(Enumerable.Repeat(int.MaxValue, closed.Length).ToArray(), 0, closed, 0, closed.Length * sizeof(int));
        }

        private static string CalcMinCost()
        {
            PriorityQueue<State> q = new PriorityQueue<State>();

            for (int i = 0; i < map.GetLength(1); i++)
            {
                if (map[0, i] < 0)
                {
                    q.Enqueue(new State(new Point(0, i, startOxy - 1), -1 * map[0, i], null));
                }
                else
                {
                    q.Enqueue(new State(new Point(0, i, SupplyOxy(startOxy - 1, 0, i)), 0, null));
                }
            }

            while (q.Count > 0)
            {
                State cur = q.Dequeue();
                if (cur.P.RestOxy == 0) continue;
                if (cur.P.I == map.GetLength(1) - 1) return cur.Cost.ToString();

                for (int i = 0; i < di.Length; i++)
                {
                    int nextI = cur.P.I + di[i];
                    int nextJ = cur.P.J + dj[i];
                    if (!CanMove(nextJ)) continue;

                    Point nextP;
                    if (map[nextI, nextJ] < 0)
                    {
                        nextP = new Point(nextI, nextJ, cur.P.RestOxy - 1);
                        if (!cur.Path.Contains(nextP))
                        {
                            int nextCost = cur.Cost + -1 * map[nextI, nextJ];
                            if (nextCost <= maxCost)
                            {
                                q.Enqueue(new State(nextP, nextCost, cur.Path));
                            }
                        }
                    }
                    else
                    {
                        nextP = new Point(nextI, nextJ, SupplyOxy(cur.P.RestOxy - 1, nextI, nextJ));
                        if (!cur.Path.Contains(nextP))
                        {
                            q.Enqueue(new State(nextP, cur.Cost, cur.Path));
                        }
                    }
                }
            }
            return "NA";
        }

        private static bool CanMove(int j)
        {
            return 0 < j && j < map.GetLength(1);
        }

        private static int SupplyOxy(int curOxy, int i, int j)
        {
            return Math.Min(maxOxy, curOxy + map[i, j]);
        }

        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[] RArSt(char sep = ' ') { return Console.ReadLine().Trim().Split(sep); }
        static int[] RArInt(char sep = ' ') { return Array.ConvertAll(Console.ReadLine().Trim().Split(sep), e => int.Parse(e)); }
        static long[] RArLong(char sep = ' ') { return Array.ConvertAll(Console.ReadLine().Trim().Split(sep), e => long.Parse(e)); }
        static double[] RArDouble(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];
        }
    }
}