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

20-003 ナンバースクエア

問題

小田原充宏さんのナンバースクエアを解きます。

10個のピースは下記となります。


問1.11×11に入れる (ピースの裏返しは禁止)


問2.5×25に入れる (ピースの裏返しは禁止)


問3.6×21に入れる (ピースの裏返しは禁止)


問4.7×18に入れる (ピースの裏返しは禁止)


問5.9×14に入れる (ピースの裏返しは禁止)


問6.8×15に入れる (ピースの裏返しは可)


問7.すき間にもう1つ「1」が入るように11×11に入れる (ピースの裏返しは可)


ソース(作成中)

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

class Program
{
    //ピースごとの配置候補
    static Dictionary<char, List<bool[,]>> HaitiKouhoListDict =
        new Dictionary<char, List<bool[,]>>();

    //ピースごとの必要面積のDict
    static Dictionary<char, int> MensekiDict = new Dictionary<char, int>();

    static char[] PieceArr = "0123456789".ToCharArray();

    static int mQuestionNo = 1;
    static char[,] mQuestionArr; //問題の初期盤面
    static int mNeedMenseki;

    static int UB_X;
    static int UB_Y;

    struct JyoutaiDef
    {
        internal char[,] BanArr;
        internal int CurrX;
        internal int CurrY;
        internal List<char> UsedPieceList;
    }

    //問題を定義
    static void QuestionDef()
    {
        if (mQuestionNo == 1) { UB_X = 11 - 1; UB_Y = 11 - 1; }
        if (mQuestionNo == 2) { UB_X = 25 - 1; UB_Y = 5 - 1; }
        if (mQuestionNo == 3) { UB_X = 21 - 1; UB_Y = 6 - 1; }
        if (mQuestionNo == 4) { UB_X = 18 - 1; UB_Y = 7 - 1; }
        if (mQuestionNo == 5) { UB_X = 14 - 1; UB_Y = 9 - 1; }
        if (mQuestionNo == 6) { UB_X = 15 - 1; UB_Y = 8 - 1; }
        if (mQuestionNo == 7) { UB_X = 11 - 1; UB_Y = 11 - 1; }

        mQuestionArr = new char[UB_X + 1, UB_Y + 1];
        for (int X = 0; X <= UB_X; X++)
            for (int Y = 0; Y <= UB_Y; Y++)
                mQuestionArr[X, Y] = ' ';
    }

    static void Main()
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();

        //問題を定義
        QuestionDef();

        foreach (char EachPiece in PieceArr) {
            HaitiKouhoListDict[EachPiece] = DeriveHaitiKouhoList(EachPiece);

            MensekiDict[EachPiece] = HaitiKouhoListDict[EachPiece][0].Cast<bool>().Count(A => A);
            mNeedMenseki += MensekiDict[EachPiece];
        }
        if (mQuestionNo == 7) mNeedMenseki += 5; //ピース1の分

        //回転解の除外
        //if (mQuestionNo == 1)
        //    HaitiKouhoListDict['3'].RemoveRange(1, HaitiKouhoListDict['3'].Count - 1);
        if (mQuestionNo == 6)
            HaitiKouhoListDict['3'].RemoveRange(2, HaitiKouhoListDict['3'].Count - 2);
        if (mQuestionNo == 7)
            HaitiKouhoListDict['3'].RemoveRange(1, HaitiKouhoListDict['3'].Count - 1);

        var sb = new System.Text.StringBuilder();

        foreach (var EachPair in HaitiKouhoListDict.OrderBy(A => A.Key)) {
            sb.AppendFormat("{0}の回転は", EachPair.Key);
            sb.AppendLine();
            foreach (bool[,] EachHaiti in EachPair.Value) {
                for (int Y = 0; Y <= EachHaiti.GetUpperBound(1); Y++) {
                    for (int X = 0; X <= EachHaiti.GetUpperBound(0); X++) {
                        sb.Append(EachHaiti[X, Y] ? '■' : ' ');
                    }
                    sb.AppendLine();
                }
                sb.AppendLine();
            }
        }
        //Console.WriteLine(sb.ToString());
        //return;

        var Stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        WillPush.BanArr = mQuestionArr;
        WillPush.CurrX = WillPush.CurrY = 0;
        WillPush.UsedPieceList = new List<char>();
        Stk.Push(WillPush);

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

            //if (PopID < 5000) {
            //    PrintBan(Popped.BanArr);
            //}

            if (++PopID % 500000 == 0) {
                Console.WriteLine("PopID={0}", PopID);
                PrintBan(Popped.BanArr);
            }

            //クリア判定
            if (Popped.UsedPieceList.Count == (mQuestionNo == 7 ? 11 : 10)) {
                Console.WriteLine("解を発見。経過時間={0}", sw.Elapsed);
                PrintBan(Popped.BanArr);
                break;
            }

            //X座標の繰上げ処理
            if (Popped.CurrX > UB_X) {
                Popped.CurrX = 0;
                Popped.CurrY++;

                //右側が全て空白なら枝切り
                bool WillContinue = true;
                for (int Y = 0; Y <= Popped.CurrY - 1; Y++) {
                    if (Popped.BanArr[UB_X, Y] != ' ')
                        WillContinue = false;
                }
                if (WillContinue) continue;

                //残り面積で枝切り
                int RestMenseki = 0;
                for (int X = 0; X <= UB_X; X++) {
                    for (int Y = Popped.CurrY; Y <= UB_Y; Y++) {
                        if (Popped.BanArr[X, Y] == ' ') RestMenseki++;
                    }
                }
                foreach (char EachPiece in PieceArr) {
                    if (Popped.UsedPieceList.Contains(EachPiece)) continue;
                    RestMenseki -= MensekiDict[EachPiece];
                }
                if (RestMenseki < 0) continue;

                if (Popped.CurrY >= UB_Y - 1) {
                    if (Popped.UsedPieceList.Contains('0') == false) continue;
                    if (Popped.UsedPieceList.Contains('2') == false) continue;
                    if (Popped.UsedPieceList.Contains('3') == false) continue;
                    if (Popped.UsedPieceList.Contains('4') == false) continue;
                    if (Popped.UsedPieceList.Contains('5') == false) continue;
                    if (Popped.UsedPieceList.Contains('6') == false) continue;
                    if (Popped.UsedPieceList.Contains('7') == false) continue;
                    if (Popped.UsedPieceList.Contains('8') == false) continue;
                    if (Popped.UsedPieceList.Contains('9') == false) continue;
                }

                //最終行を超えた場合
                if (Popped.CurrY > UB_Y) continue;
            }

            foreach (char EachPiece in PieceArr) {
                if (mQuestionNo == 7 && EachPiece == 1) {
                    if (Popped.UsedPieceList.Count(A => A == 1) == 2) {
                        continue;
                    }
                }
                else if (Popped.UsedPieceList.Contains(EachPiece))
                    continue;

                //0と8は、0を先に使う
                if (EachPiece == '8' && Popped.UsedPieceList.Contains('0') == false)
                    continue;

                //2と5は、2を先に使う
                if (mQuestionNo == 6 || mQuestionNo == 7)
                    if (EachPiece == '5' && Popped.UsedPieceList.Contains('2') == false)
                        continue;

                //6と9は、6を先に使う
                if (EachPiece == '9' && Popped.UsedPieceList.Contains('6') == false)
                    continue;

                //ピースの配置候補リスト
                List<bool[,]> HaitiKouhoList = new List<bool[,]>();
                HaitiKouhoList.AddRange(HaitiKouhoListDict[EachPiece]);

                //マス目にピースを埋めれない候補をRemove
                HaitiKouhoList.RemoveAll(X =>
                    CanFillPiece(X, Popped.CurrX, Popped.CurrY, Popped.BanArr) == false);

                //ピースを配置する経路のPush処理
                foreach (bool[,] EachPieceMap in HaitiKouhoList) {

                    //2問目の特殊枝切り
                    if (mQuestionNo == 2) {
                        //縦に置く場合は、018の順序とする
                        if (EachPiece == '0' && EachPieceMap.GetLength(1) == 5) {
                            if (Popped.CurrX != UB_X - 2
                            && Popped.CurrX != UB_X - 3
                            && Popped.CurrX != UB_X - 6) continue;
                        }
                        if (EachPiece == '1' && EachPieceMap.GetLength(1) == 5) {
                            if (Popped.CurrX != UB_X - 3
                            && Popped.CurrX != UB_X) continue;
                        }
                        if (EachPiece == '8' && EachPieceMap.GetLength(1) == 5) {
                            if (Popped.CurrX != UB_X - 2) continue;
                        }
                    }

                    WillPush.BanArr = (char[,])Popped.BanArr.Clone();
                    WillPush.CurrX = Popped.CurrX + 1;
                    WillPush.CurrY = Popped.CurrY;
                    WillPush.UsedPieceList = new List<char>(Popped.UsedPieceList);
                    WillPush.UsedPieceList.Add(EachPiece);

                    for (int X = 0; X <= EachPieceMap.GetUpperBound(0); X++) {
                        for (int Y = 0; Y <= EachPieceMap.GetUpperBound(1); Y++) {
                            if (EachPieceMap[X, Y] == false) continue;
                            WillPush.BanArr[Popped.CurrX + X, Popped.CurrY + Y] = EachPiece;
                        }
                    }

                    if (EachPiece == '7') WillPush.CurrX = Popped.CurrX + 1;
                    else WillPush.CurrX = Popped.CurrX + EachPieceMap.GetLength(0);

                    //1個目のピースを配置後の枝切り
                    if (Popped.CurrX == 0 && Popped.CurrY == 0 && WillEdakiri(WillPush.BanArr))
                        continue;

                    Stk.Push(WillPush);
                }
            }
            //ピースを配置しない経路のPush
            WillPush.BanArr = Popped.BanArr;
            WillPush.CurrX = Popped.CurrX + 1;
            WillPush.CurrY = Popped.CurrY;
            WillPush.UsedPieceList = Popped.UsedPieceList;

            Stk.Push(WillPush);
        }
    }

    //1個目のピースを配置後の枝切り
    static bool WillEdakiri(char[,] pBanArr)
    {
        if (pBanArr[3, 0] == '7' && pBanArr[4, 0] == '7'
         && pBanArr[4, 1] == '7' && pBanArr[4, 2] == '7'
         && pBanArr[3, 2] == '7' && pBanArr[2, 2] == '7'
         && pBanArr[1, 2] == '7' && pBanArr[0, 2] == '7') return true;

        //2問目の特殊枝切り
        if (mQuestionNo == 2) {
            if (pBanArr[0, 0] == '7' && pBanArr[2, 0] == '7'
             && pBanArr[2, 4] == '7' && pBanArr[0, 1] == '7') return true;
        }

        if (pBanArr[0, 0] == '6' && pBanArr[4, 1] == '6' && pBanArr[4, 2] == '6') return true;
        if (pBanArr[0, 0] == '6' && pBanArr[2, 4] == '6' && pBanArr[2, 1] == '6') return true;

        if (pBanArr[4, 0] == '4' && pBanArr[4, 2] == '4' && pBanArr[0, 2] == '4') return true;

        //2問目の特殊枝切り
        if (mQuestionNo == 2) {
            if (pBanArr[0, 0] == '4' && pBanArr[2, 0] == '4' && pBanArr[2, 4] == '4') return true;
        }

        if (pBanArr[0, 0] == '3' && pBanArr[2, 0] == '3'
         && pBanArr[2, 4] == '3' && pBanArr[2, 1] == '3') return true;

        return false;
    }

    //ピース名を引数として、回転させた配置のListを返す
    static List<bool[,]> DeriveHaitiKouhoList(char pPiece)
    {
        bool[,] wkArr = null;

        //■■■
        //■ ■
        //■ ■
        //■ ■
        //■■■
        if (pPiece == '0') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[1, 0] = wkArr[2, 0] = true;
            wkArr[0, 1] = wkArr[2, 1] = true;
            wkArr[0, 2] = wkArr[2, 2] = true;
            wkArr[0, 3] = wkArr[2, 3] = true;
            wkArr[0, 4] = wkArr[1, 4] = wkArr[2, 4] = true;
        }

        //■
        //■
        //■
        //■
        //■
        if (pPiece == '1') {
            wkArr = new bool[1, 5];
            wkArr[0, 0] = true;
            wkArr[0, 1] = true;
            wkArr[0, 2] = true;
            wkArr[0, 3] = true;
            wkArr[0, 4] = true;
        }

        //■■■
        //  ■
        //■■■
        //■
        //■■■
        if (pPiece == '2') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[1, 0] = wkArr[2, 0] = true;
            wkArr[2, 1] = true;
            wkArr[0, 2] = wkArr[1, 2] = wkArr[2, 2] = true;
            wkArr[0, 3] = true;
            wkArr[0, 4] = wkArr[1, 4] = wkArr[2, 4] = true;
        }

        //■■■
        //  ■
        //■■■
        //  ■
        //■■■
        if (pPiece == '3') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[1, 0] = wkArr[2, 0] = true;
            wkArr[2, 1] = true;
            wkArr[0, 2] = wkArr[1, 2] = wkArr[2, 2] = true;
            wkArr[2, 3] = true;
            wkArr[0, 4] = wkArr[1, 4] = wkArr[2, 4] = true;
        }

        //■ ■
        //■ ■
        //■■■
        //  ■
        //  ■
        if (pPiece == '4') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[2, 0] = true;
            wkArr[0, 1] = wkArr[2, 1] = true;
            wkArr[0, 2] = wkArr[1, 2] = wkArr[2, 2] = true;
            wkArr[2, 3] = true;
            wkArr[2, 4] = true;
        }

        //■■■
        //■
        //■■■
        //  ■
        //■■■
        if (pPiece == '5') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[1, 0] = wkArr[2, 0] = true;
            wkArr[0, 1] = true;
            wkArr[0, 2] = wkArr[1, 2] = wkArr[2, 2] = true;
            wkArr[2, 3] = true;
            wkArr[0, 4] = wkArr[1, 4] = wkArr[2, 4] = true;
        }

        //■■■
        //■
        //■■■
        //■ ■
        //■■■
        if (pPiece == '6') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[1, 0] = wkArr[2, 0] = true;
            wkArr[0, 1] = true;
            wkArr[0, 2] = wkArr[1, 2] = wkArr[2, 2] = true;
            wkArr[0, 3] = wkArr[2, 3] = true;
            wkArr[0, 4] = wkArr[1, 4] = wkArr[2, 4] = true;
        }

        //■■■
        //■ ■
        //  ■
        //  ■
        //  ■
        if (pPiece == '7') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[1, 0] = wkArr[2, 0] = true;
            wkArr[0, 1] = wkArr[2, 1] = true;
            wkArr[2, 2] = true;
            wkArr[2, 3] = true;
            wkArr[2, 4] = true;
        }

        //■■■
        //■ ■
        //■■■
        //■ ■
        //■■■
        if (pPiece == '8') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[1, 0] = wkArr[2, 0] = true;
            wkArr[0, 1] = wkArr[2, 1] = true;
            wkArr[0, 2] = wkArr[1, 2] = wkArr[2, 2] = true;
            wkArr[0, 3] = wkArr[2, 3] = true;
            wkArr[0, 4] = wkArr[1, 4] = wkArr[2, 4] = true;
        }

        //■■■
        //■ ■
        //■■■
        //  ■
        //■■■
        if (pPiece == '9') {
            wkArr = new bool[3, 5];
            wkArr[0, 0] = wkArr[1, 0] = wkArr[2, 0] = true;
            wkArr[0, 1] = wkArr[2, 1] = true;
            wkArr[0, 2] = wkArr[1, 2] = wkArr[2, 2] = true;
            wkArr[2, 3] = true;
            wkArr[0, 4] = wkArr[1, 4] = wkArr[2, 4] = true;
        }
        return DeriveKaitenArrList(wkArr);
    }

    //配列を引数として、回転させた配列のリストをDistinctして返す
    static List<bool[,]> DeriveKaitenArrList(bool[,] pBaseArr)
    {
        var KaitenArrList = new List<bool[,]>();

        int BaseArrUB_X = pBaseArr.GetUpperBound(0);
        int BaseArrUB_Y = pBaseArr.GetUpperBound(1);

        for (int I = 1; I <= 8; I++) KaitenArrList.Add(null);
        for (int P = 0; P <= 6; P += 2) KaitenArrList[P] = new bool[BaseArrUB_X + 1, BaseArrUB_Y + 1];
        for (int P = 1; P <= 7; P += 2) KaitenArrList[P] = new bool[BaseArrUB_Y + 1, BaseArrUB_X + 1];

        for (int X = 0; X <= BaseArrUB_X; X++) {
            for (int Y = 0; Y <= BaseArrUB_Y; Y++) {
                bool SetVal = pBaseArr[X, Y];
                KaitenArrList[0][X, Y] = SetVal;
                KaitenArrList[1][Y, BaseArrUB_X - X] = SetVal;
                KaitenArrList[2][BaseArrUB_X - X, BaseArrUB_Y - Y] = SetVal;
                KaitenArrList[3][BaseArrUB_Y - Y, X] = SetVal;

                KaitenArrList[4][X, BaseArrUB_Y - Y] = SetVal;
                KaitenArrList[5][BaseArrUB_Y - Y, BaseArrUB_X - X] = SetVal;
                KaitenArrList[6][BaseArrUB_X - X, Y] = SetVal;
                KaitenArrList[7][Y, X] = SetVal;
            }
        }

        //問6と問7以外はピースの裏返し不可
        if (mQuestionNo != 6 && mQuestionNo != 7) {
            KaitenArrList.RemoveRange(4, 4);
        }

        //Distinctする
        for (int I = KaitenArrList.Count - 1; 0 <= I; I--) {
            for (int J = 0; J <= I - 1; J++) {
                if (KaitenArrList[I].GetUpperBound(0) !=
                    KaitenArrList[J].GetUpperBound(0)) continue;
                if (KaitenArrList[I].GetUpperBound(1) !=
                    KaitenArrList[J].GetUpperBound(1)) continue;

                IEnumerable<bool> wkEnum1 = KaitenArrList[I].Cast<bool>();
                IEnumerable<bool> wkEnum2 = KaitenArrList[J].Cast<bool>();
                if (wkEnum1.SequenceEqual(wkEnum2) == false) continue;

                KaitenArrList.RemoveAt(I);
                break;
            }
        }
        return KaitenArrList;
    }

    //マス目にピースを埋めれるか
    static bool CanFillPiece(bool[,] pPieceMap, int pTargetX, int pTargetY, char[,] pBanArr)
    {
        if (pTargetX + pPieceMap.GetUpperBound(0) > UB_X) return false;
        if (pTargetY + pPieceMap.GetUpperBound(1) > UB_Y) return false;

        for (int X = 0; X <= pPieceMap.GetUpperBound(0); X++) {
            for (int Y = 0; Y <= pPieceMap.GetUpperBound(1); Y++) {
                if (pPieceMap[X, Y] && pBanArr[pTargetX + X, pTargetY + Y] != ' ')
                    return false;
            }
        }

        //上が全て空白なら敷き詰め不可
        if (pTargetY > 0) {
            bool IsAllSpace = true;
            for (int X = 0; X <= pPieceMap.GetUpperBound(0); X++) {
                if (pPieceMap[X, 0] && pBanArr[pTargetX + X, pTargetY - 1] != ' ')
                    IsAllSpace = false;
            }
            if (IsAllSpace) return false;
        }

        //左が全て空白なら敷き詰め不可
        if (pTargetX > 0) {
            bool IsAllSpace = true;
            for (int Y = 0; Y <= pPieceMap.GetUpperBound(1); Y++) {
                if (pPieceMap[0, Y] && pBanArr[pTargetX - 1, pTargetY + Y] != ' ')
                    IsAllSpace = false;
            }
            if (IsAllSpace) return false;
        }

        return true;
    }

    //盤面を出力
    static void PrintBan(char[,] pBanArr)
    {
        var sb = new System.Text.StringBuilder();
        for (int Y = 0; Y <= UB_Y; Y++) {
            for (int X = 0; X <= UB_X; X++) {
                char CurrChar = pBanArr[X, Y];
                sb.Append(CurrChar == ' ' ? '/' : CurrChar);
            }
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }
}


実行結果



解説

メモ
計算量が多すぎるので、作り直しが必要