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

Cマガ電脳クラブ(第135回) Iトロミノ

問題

Fig.1のように、正方形を3つ一列につないだ1×3の形状の板をIトロミノという。
これを32枚使って、8×12の長方形にぴったり詰める。
ただし、タタミ同様、十字型の継ぎ目 (Fig.2) ができないようにしたい (もちろん、T字型はOK) 。

何通りの詰め方があるだろうか。
なお、全体を回転したものや鏡像のものは別の解とはしない。

     


ソース

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

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

    static char[] TatamiArr = { '-', '|' };

    const int UB_X = 12 - 1;
    const int UB_Y = 8 - 1;

    struct JyoutaiDef
    {
        internal char[,] TatamiNoArr;
        internal char[,] TateYokoArr;
        internal int CurrX;
        internal int CurrY;
        internal int TatamiCnt;
    }

    static void Main()
    {
        foreach (char EachTatami in TatamiArr) {
            HaitiKouhoArrDict[EachTatami] = DeriveHaitiKouhoArr(EachTatami);
        }

        var stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        WillPush.TatamiNoArr = new char[UB_X + 1, UB_Y + 1];
        WillPush.TateYokoArr = new char[UB_X + 1, UB_Y + 1];
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                WillPush.TatamiNoArr[X, Y] = ' ';
                WillPush.TateYokoArr[X, Y] = ' ';
            }
        }

        WillPush.CurrX = WillPush.CurrY = 0;
        WillPush.TatamiCnt = 0;
        stk.Push(WillPush);

        var PushedBanStrSet = new HashSet<string>();
        int AnswerCnt = 0;
        while (stk.Count > 0) {
            JyoutaiDef Popped = stk.Pop();

            //クリア判定
            if (Popped.TatamiCnt == 32) {
                AnswerCnt++;
                Console.WriteLine("解{0}を発見", AnswerCnt);
                PrintAnswer(Popped.TateYokoArr);
                continue;
            }

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

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

            //10進数をchar型に変換
            Func<int, char> DecToChar = (pInt) =>
            {
                if (pInt < 10) return (char)((int)'1' + pInt - 1);
                return (char)((int)'A' + pInt - 10);
            };

            foreach (char EachTatami in TatamiArr) {
                bool[,] HaitiKouhoArr = HaitiKouhoArrDict[EachTatami];

                //マス目にピースを埋めれない場合
                if (CanFillPiece(HaitiKouhoArr, Popped.CurrX, Popped.CurrY, Popped.TatamiNoArr) == false)
                    continue;

                //ピースを配置する経路のPush処理
                WillPush.TatamiNoArr = (char[,])Popped.TatamiNoArr.Clone();
                WillPush.TateYokoArr = (char[,])Popped.TateYokoArr.Clone();
                WillPush.CurrX = Popped.CurrX + 1;
                WillPush.CurrY = Popped.CurrY;
                WillPush.TatamiCnt = Popped.TatamiCnt + 1;

                for (int X = 0; X <= HaitiKouhoArr.GetUpperBound(0); X++) {
                    for (int Y = 0; Y <= HaitiKouhoArr.GetUpperBound(1); Y++) {
                        if (HaitiKouhoArr[X, Y] == false) continue;
                        char wkChar = DecToChar(WillPush.TatamiCnt);
                        WillPush.TatamiNoArr[Popped.CurrX + X, Popped.CurrY + Y] = wkChar;
                        WillPush.TateYokoArr[Popped.CurrX + X, Popped.CurrY + Y] = EachTatami;
                    }
                }
                if (HasJyuuji(Popped.CurrX, Popped.CurrY, WillPush.TatamiNoArr))
                    continue;

                HashSet<string> wkStrSet = BanToStrSet(WillPush.TateYokoArr);
                if (PushedBanStrSet.Overlaps(wkStrSet)) continue;
                PushedBanStrSet.Add(wkStrSet.First());

                stk.Push(WillPush);
            }

            //現在のマス目が空白でない場合は、畳を配置しない経路のPush
            if (Popped.TatamiNoArr[Popped.CurrX, Popped.CurrY] != ' ') {
                WillPush.TatamiNoArr = Popped.TatamiNoArr;
                WillPush.TateYokoArr = Popped.TateYokoArr;
                WillPush.CurrX = Popped.CurrX + 1;
                WillPush.CurrY = Popped.CurrY;
                WillPush.TatamiCnt = Popped.TatamiCnt;
                stk.Push(WillPush);
            }
        }
    }

    //盤面をString型のSetに変換
    static HashSet<string> BanToStrSet(char[,] pTateYokoArr)
    {
        var WillReturn = new HashSet<string>();

        var sbList = new List<System.Text.StringBuilder>();
        for (int I = 1; I <= 4; I++) {
            sbList.Add(new System.Text.StringBuilder());
        }
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                sbList[0].Append(pTateYokoArr[X, Y]);
                sbList[1].Append(pTateYokoArr[X, UB_Y - Y]);
                sbList[2].Append(pTateYokoArr[UB_X - X, Y]);
                sbList[3].Append(pTateYokoArr[UB_X - X, UB_Y - Y]);
            }
        }
        sbList.ForEach(X => WillReturn.Add(X.ToString()));
        return WillReturn;
    }

    //十字が存在するかを判定
    static bool HasJyuuji(int pCurrX, int pCurrY, char[,] pTatamiNoArr)
    {
        //敷き詰めるマス、左上、左、上で4枚の畳なら十字が存在している
        if (pCurrX - 1 < 0) return false;
        if (pCurrY - 1 < 0) return false;

        if (pTatamiNoArr[pCurrX - 1, pCurrY - 1] == pTatamiNoArr[pCurrX - 1, pCurrY]) return false;
        if (pTatamiNoArr[pCurrX - 1, pCurrY - 1] == pTatamiNoArr[pCurrX, pCurrY - 1]) return false;
        if (pTatamiNoArr[pCurrX - 1, pCurrY - 1] == pTatamiNoArr[pCurrX, pCurrY]) return false;
        return true;
    }

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

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

        //■
        //■
        //■
        if (pTatami == '|') {
            wkArr = new bool[1, 3];
            wkArr[0, 0] = true;
            wkArr[0, 1] = true;
            wkArr[0, 2] = true;
        }
        return wkArr;
    }

    //マス目にピースを埋めれるか
    static bool CanFillPiece(bool[,] pPieceMap, int pTargetX, int pTargetY, char[,] pTatamiNoArr)
    {
        for (int X = 0; X <= pPieceMap.GetUpperBound(0); X++) {
            if (pTargetX + X > UB_X) return false;
            for (int Y = 0; Y <= pPieceMap.GetUpperBound(1); Y++) {
                if (pTargetY + Y > UB_Y) return false;
                if (pPieceMap[X, Y] && pTatamiNoArr[pTargetX + X, pTargetY + Y] != ' ')
                    return false;
            }
        }
        return true;
    }

    //解を出力
    static void PrintAnswer(char[,] pTatamiNoArr)
    {
        var sb = new System.Text.StringBuilder();
        for (int Y = 0; Y <= UB_Y; Y++) {
            for (int X = 0; X <= UB_X; X++) {
                sb.Append(pTatamiNoArr[X, Y]);
            }
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }
}


実行結果

解1を発見
|||---------
||||---|---|
||||---|---|
---|---|---|
||---|------
||---|||---|
||---|||---|
------||---|

解2を発見
|||---------
||||---|---|
||||---|---|
---|---|---|
||---|------
||---||---||
||---||---||
------|---||


解説

回転解を排除しながら、深さ優先探索で、畳を敷き詰めてます。

プログラマ脳を鍛える数学パズル Q32 畳を敷きつめろ