トップページに戻る    次のC#のサンプルへ    前のC#のサンプルへ

20-100 難行苦行

問題

小田原充宏さんの難行苦行を解きます。



各ピースをスライドさせて
下記にすればクリアです。
なんぎょう
□くぎょう

「ぎ」と「ょ」はくっついてます。
最少手数は、123手です。


ソース

using System;
using System.Collections.Generic;

class Program
{
    const int UB_X = 4;
    const int UB_Y = 1;

    static char[,] mQuestionArr = new char[UB_X + 1, UB_Y + 1]; //問題の初期盤面

    //問題を定義
    static void QuestionDef()
    {
        string[] wkArr ={"□くぎょう",
                         "なんぎょう"};

        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                mQuestionArr[X, Y] = wkArr[Y][X];
            }
        }
    }

    struct JyoutaiDef
    {
        internal char[,] BanArr;
        internal int Level;
        internal List<string> Log;
    }

    static void Main()
    {
        //問題を定義
        QuestionDef();

        var Stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        WillPush.BanArr = mQuestionArr;
        WillPush.Level = 0;
        WillPush.Log = new List<string>();
        WillPush.Log.Add(BanToStr(WillPush.BanArr));
        Stk.Push(WillPush);

        //盤面に対する最少手数のDict
        var MinTesuuDict = new Dictionary<string, int>();
        MinTesuuDict.Add(GetHash(WillPush.BanArr), 0);

        List<string> AnswerLog = null;
        int AnswerLevel = int.MaxValue;

        while (Stk.Count > 0) {
            JyoutaiDef Popped = Stk.Pop();

            if (IsClear(Popped.BanArr)) {
                if (AnswerLog == null && Popped.Level == AnswerLevel
                 || Popped.Level < AnswerLevel) {
                    AnswerLog = Popped.Log;
                    AnswerLevel = Popped.Level;
                    Console.WriteLine("{0}手の解を発見", Popped.Level);
                }
                continue;
            }
            //下限値枝切り
            if (Popped.Level + DeriveNeedMinTesuu(Popped.BanArr) > AnswerLevel) continue;

            //空きマスを求める
            int X = 0, Y = 0;
            while (Popped.BanArr[X, Y] != '□') {
                if (++X > UB_X) {
                    Y++; X = 0;
                }
            }

            Action<int, int> PushSyori = (pFromX, pFromY) =>
            {
                WillPush.BanArr = (char[,])Popped.BanArr.Clone();
                WillPush.BanArr[X, Y] = WillPush.BanArr[pFromX, pFromY];
                WillPush.BanArr[pFromX, pFromY] = '□';
                WillPush.Level = Popped.Level + 1;

                int HeniX = X - pFromX;

                //「ぎ」の左移動は、「ょ」が連動する
                if (HeniX == -1 && Popped.BanArr[pFromX, pFromY] == 'ぎ') {
                    WillPush.BanArr[pFromX, pFromY] = 'ょ';
                    WillPush.BanArr[pFromX - HeniX, pFromY] = '□';
                }

                //「ょ」の右移動は、「ぎ」が連動する
                if (HeniX == 1 && Popped.BanArr[pFromX, pFromY] == 'ょ') {
                    WillPush.BanArr[pFromX, pFromY] = 'ぎ';
                    WillPush.BanArr[pFromX - HeniX, pFromY] = '□';
                }

                //当該盤面に、少ない手数か等しい手数で、到達済なら枝切り
                string Hash = GetHash(WillPush.BanArr);
                int MinTesuu;
                if (MinTesuuDict.TryGetValue(Hash, out MinTesuu)) {
                    if (MinTesuu <= WillPush.Level) return;
                }
                MinTesuuDict[Hash] = WillPush.Level;

                WillPush.Log = new List<string>(Popped.Log);
                WillPush.Log.Add(BanToStr(WillPush.BanArr));
                Stk.Push(WillPush);
            };

            //左の数字を、右に移動
            if (X > 0) PushSyori(X - 1, Y);

            //右の数字を、左に移動
            if (X < UB_X) PushSyori(X + 1, Y);

            //上の数字を、下に移動
            if (Y > 0 && Popped.BanArr[X, Y - 1] != 'ぎ'
                      && Popped.BanArr[X, Y - 1] != 'ょ')
                PushSyori(X, Y - 1);

            //下の数字を、上に移動
            if (Y < UB_Y && Popped.BanArr[X, Y + 1] != 'ぎ'
                         && Popped.BanArr[X, Y + 1] != 'ょ')
                PushSyori(X, Y + 1);
        }
        for (int I = 0; I <= AnswerLog.Count - 1; I++) {
            Console.WriteLine("{0}手目", I);
            Console.WriteLine(AnswerLog[I]);
        }
    }

    //クリア判定
    static bool IsClear(char[,] pBanArr)
    {
        if (pBanArr[0, 0] != 'な') return false;
        if (pBanArr[1, 0] != 'ん') return false;
        if (pBanArr[2, 0] != 'ぎ') return false;
        if (pBanArr[4, 0] != 'う') return false;

        if (pBanArr[1, 1] != 'く') return false;
        if (pBanArr[2, 1] != 'ぎ') return false;
        if (pBanArr[4, 1] != 'う') return false;
        return true;
    }

    //最小の残り手数を返す(下限値枝切り用)
    static int DeriveNeedMinTesuu(char[,] pBanArr)
    {
        int WillReturn = 0;
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                if (pBanArr[X, Y] == 'な') {
                    WillReturn += DeriveKyori(X, Y, 0, 0);
                }
                if (pBanArr[X, Y] == 'ん') {
                    WillReturn += DeriveKyori(X, Y, 1, 0);
                }
                if (pBanArr[X, Y] == 'ぎ') {
                    int Kyori1 = DeriveKyori(X, Y, 2, 0);
                    int Kyori2 = DeriveKyori(X, Y, 2, 1);
                    WillReturn += Math.Min(Kyori1, Kyori2);
                }
                if (pBanArr[X, Y] == 'う') {
                    int Kyori1 = DeriveKyori(X, Y, 4, 0);
                    int Kyori2 = DeriveKyori(X, Y, 4, 1);
                    WillReturn += Math.Min(Kyori1, Kyori2);
                }
                if (pBanArr[X, Y] == 'く') {
                    WillReturn += DeriveKyori(X, Y, 1, 1);
                }
            }
        }
        return WillReturn;
    }

    //2点間のマンハッタン距離を求める
    static int DeriveKyori(int pX1, int pY1, int pX2, int pY2)
    {
        return Math.Abs(pX1 - pX2) + Math.Abs(pY1 - pY2);
    }

    //ハッシュ値を求める
    static string GetHash(char[,] pBanArr)
    {
        var sb = new System.Text.StringBuilder();
        for (int Y = 0; Y <= UB_Y; Y++) {
            for (int X = 0; X <= UB_X; X++) {
                sb.Append(pBanArr[X, Y]);
            }
        }
        return sb.ToString();
    }

    //盤面をString型で表現
    static string BanToStr(char[,] pBanArr)
    {
        var sb = new System.Text.StringBuilder();
        for (int Y = 0; Y <= UB_Y; Y++) {
            for (int X = 0; X <= UB_X; X++) {
                sb.Append(pBanArr[X, Y]);
            }
            sb.AppendLine();
        }
        return sb.ToString();
    }
}


実行結果

省略
121手目
なんぎょう
くぎょ□う

122手目
なんぎょう
く□ぎょう

123手目
なんぎょう
□くぎょう


解説

深さ優先探索で解いてます。