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

24-08 クロット

問題

ニコリのクロットを解きます。

例題と答え
    

1 盤面のいくつかのマスを黒くぬりましょう。

2 丸の中の数字は、その数字の入っているマスにタテヨコに隣り合うマスが含まれる、
  タテヨコでひとつながりになった黒マスの個数の合計を表します。
  数字のない丸では、いくつの黒マスになるかわかりません。

3 丸のあるマスを黒くぬってはいけません。


ソース

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

class Program
{
    static int UB_X;
    static int UB_Y;

    struct PointDef
    {
        internal int X;
        internal int Y;
    }

    struct JyoutaiDef
    {
        internal char[,] BanArr;
        internal int CurrP;
    }

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

        //盤面定義
        //黒マスは*
        //白マスは半角空白
        //白マス(確定)は/
        //数字のある丸は、0から9とAからZの36進数
        //数字のない丸は?

        char[,] Q01Arr = {{'2',' ',' ',' ',},
                          {'0','3',' ','?',},
                          {' ',' ',' ','1',},
                          {' ','1',' ',' ',}};

        char[,] WkArr = Q01Arr;

        UB_X = WkArr.GetUpperBound(1);
        UB_Y = WkArr.GetUpperBound(0);

        char[,] QuestionArr = new char[UB_X + 1, UB_Y + 1];
        for (int X = 0; X <= UB_X; X++)
            for (int Y = 0; Y <= UB_Y; Y++)
                QuestionArr[X, Y] = WkArr[Y, X];

        PointDef[] SortdSuujiMasuArr = DeriveSortdSuujiMasuArr(QuestionArr);

        var stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        WillPush.BanArr = QuestionArr;
        WillPush.CurrP = 0;
        stk.Push(WillPush);

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

            //最終的な解判定
            if (CheckBan(Popped.BanArr, true)) {
                Console.WriteLine("解を発見。経過時間={0}", sw.Elapsed);
                PrintAnswer(Popped.BanArr);
                return;
            }

            PointDef CurrPoint = SortdSuujiMasuArr[Popped.CurrP];
            char CurrMasu = Popped.BanArr[CurrPoint.X, CurrPoint.Y];

            //必要な連結黒マス数
            int NeedRenketuBlackCnt = StrToDec(CurrMasu);

            //数字マスに隣接した黒マスの座標List
            List<PointDef> RinsetuBlackList =
                DeriveRinsetuMasuList(Popped.BanArr, CurrPoint.X, CurrPoint.Y, '*');

            //数字マスに連結した黒マスの座標List
            List<PointDef> RenketuBlackList =
                DeriveRenketuMasuList(Popped.BanArr, RinsetuBlackList, '*', NeedRenketuBlackCnt + 1);

            //現在の連結黒マス数
            int CurrRenketuBlackCnt = RenketuBlackList.Count;

            //現在の数字マスを黒マスとしてAdd
            RenketuBlackList.Add(new PointDef() { X = CurrPoint.X, Y = CurrPoint.Y });

            //連結黒マスに、隣接した白マスの座標List
            List<PointDef> RenketuWhiteListAll = new List<PointDef>();

            foreach (var EachPoint in RenketuBlackList) {
                List<PointDef> RenketuWhiteListEach =
                    DeriveRinsetuMasuList(Popped.BanArr, EachPoint.X, EachPoint.Y, ' ');

                RenketuWhiteListAll.AddRange(RenketuWhiteListEach);
            }

            //現在の連結黒マス数 > 必要な連結黒マス数の場合は、Pushする前に枝切り済

            //現在の連結黒マス数 = 必要な連結黒マス数なら、Push処理
            if (CurrRenketuBlackCnt == NeedRenketuBlackCnt) {
                WillPush.BanArr = (char[,])Popped.BanArr.Clone();

                //連結した白マスを、確定白マスに設定
                RenketuWhiteListAll.ForEach(A => WillPush.BanArr[A.X, A.Y] = '/');

                WillPush.CurrP = Popped.CurrP + 1;
                stk.Push(WillPush);
                continue;
            }

            //現在の連結黒マス数 < 必要な連結黒マス数なら、黒マスを配置

            //黒マス配置不可な白マスを確定白マスに設定
            for (int I = RenketuWhiteListAll.Count - 1; 0 <= I; I--) {
                PointDef wkPoint = RenketuWhiteListAll[I];

                char[,] wkBan = (char[,])Popped.BanArr.Clone();
                wkBan[wkPoint.X, wkPoint.Y] = '*';

                if (CheckBan(wkBan, false) == false) {
                    Popped.BanArr[wkPoint.X, wkPoint.Y] = '/';
                    RenketuWhiteListAll.RemoveAt(I);
                }
            }

            //黒マスを配置
            for (int I = 0; I <= RenketuWhiteListAll.Count - 1; I++) {
                PointDef wkPoint1 = RenketuWhiteListAll[I];

                WillPush.BanArr = (char[,])Popped.BanArr.Clone();
                WillPush.BanArr[wkPoint1.X, wkPoint1.Y] = '*';

                //黒マスを配置しなかった白マスは、確定白マスに設定しておく
                for (int J = 0; J <= I - 1; J++) {
                    PointDef wkPoint2 = RenketuWhiteListAll[J];
                    WillPush.BanArr[wkPoint2.X, wkPoint2.Y] = '/';
                }

                WillPush.CurrP = Popped.CurrP;
                stk.Push(WillPush);
            }
        }
    }

    //小さい数字の黒マスから埋めていくので、
    //必要黒マス数でソートした座標の配列を返す
    static PointDef[] DeriveSortdSuujiMasuArr(char[,] pQuestionArr)
    {
        var WillReturnList = new List<PointDef>();

        for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
            for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                char CurrMasu = pQuestionArr[LoopX, LoopY];
                //黒マスを埋める必要があるマスの場合
                if ('0' <= CurrMasu && CurrMasu <= '9' ||
                    'A' <= CurrMasu && CurrMasu <= 'Z') {
                    WillReturnList.Add(new PointDef() { X = LoopX, Y = LoopY });
                }
            }
        }

        var tmp1 = WillReturnList.OrderBy(A => StrToDec(pQuestionArr[A.X, A.Y]));
        var tmp2 = tmp1.ThenBy(A => A.X * A.X + A.Y * A.Y);
        return tmp1.ToArray();
    }

    //char型を10進数に変換
    static int StrToDec(char pStr)
    {
        if (pStr == ' ') return 0;
        if ('A' <= pStr) return 10 + pStr - 'A';
        return (int)(pStr - '0');
    }

    //pIsAnswerHanteiがTrueなら、盤面の解判定の結果を返す
    //pIsAnswerHanteiがFalseなら、盤面の有効判定の結果を返す
    static bool CheckBan(char[,] pBanArr, bool pIsAnswerHantei)
    {
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                char CurrMasu = pBanArr[X, Y];

                //黒マスの数が指定されてる場合
                if ('0' <= CurrMasu && CurrMasu <= '9' ||
                    'A' <= CurrMasu && CurrMasu <= 'Z') {

                    //数字マスに隣接した黒マスか白マスの座標List
                    List<PointDef> RinsetuKouhoList =
                        DeriveRinsetuMasuList(pBanArr, X, Y, 'B');

                    //必要な連結黒マス数
                    int NeedRenketuBlackCnt = StrToDec(CurrMasu);

                    //数字マスに連結した黒マスか白マスの座標List
                    List<PointDef> RenketuKouhoList =
                        DeriveRenketuMasuList(pBanArr, RinsetuKouhoList, 'B', NeedRenketuBlackCnt);

                    //数字マスに隣接した黒マスの座標List
                    List<PointDef> RinsetuBlackList =
                        DeriveRinsetuMasuList(pBanArr, X, Y, '*');

                    //数字マスに連結した黒マスの座標List
                    List<PointDef> RenketuBlackList =
                        DeriveRenketuMasuList(pBanArr, RinsetuBlackList, '*', NeedRenketuBlackCnt + 1);

                    //確定白マスにより、必要黒マス数を満たせなくなった場合
                    if (NeedRenketuBlackCnt > RenketuKouhoList.Count)
                        return false;

                    //黒マス必要数 < 現在の黒マス数 な場合
                    if (NeedRenketuBlackCnt < RenketuBlackList.Count)
                        return false;

                    //黒マス数が不一致だったら、解判定ではNG
                    if (pIsAnswerHantei && RenketuBlackList.Count != NeedRenketuBlackCnt)
                        return false;
                }
            }
        }
        return true;
    }

    //指定座標に隣接した、指定マスの、座標Listを返す
    static List<PointDef> DeriveRinsetuMasuList(char[,] pBanArr, int pBaseX, int pBaseY,
                                                char pMasuKind)
    {
        var WillReturnList = new List<PointDef>();

        Action<int, int> wkAct = (pX, pY) =>
        {
            //指定マスでない場合
            if (pMasuKind == ' ' && pBanArr[pX, pY] != ' ') return;
            if (pMasuKind == '*' && pBanArr[pX, pY] != '*') return;
            if (pMasuKind == 'B' && pBanArr[pX, pY] != ' ' && pBanArr[pX, pY] != '*') return;

            WillReturnList.Add(new PointDef() { X = pX, Y = pY });
        };

        if (pBaseX > 0) wkAct(pBaseX - 1, pBaseY);
        if (pBaseX < UB_X) wkAct(pBaseX + 1, pBaseY);
        if (pBaseY > 0) wkAct(pBaseX, pBaseY - 1);
        if (pBaseY < UB_Y) wkAct(pBaseX, pBaseY + 1);

        return WillReturnList;
    }

    //指定マスの座標Listを引数として、連結した指定マスを含めた、座標Listを返す
    static List<PointDef> DeriveRenketuMasuList(char[,] pBanArr, List<PointDef> pRinsetuBlackList,
                                                char pMasuKind, int pJyougen)
    {
        var VisitedList = new List<PointDef>(); //訪問済座標のセット

        var stk = new Stack<PointDef>();
        PointDef WillPush;

        Action<int, int> PushSyori = (pNewX, pNewY) =>
        {
            //指定マスでない場合
            if (pMasuKind == ' ' && pBanArr[pNewX, pNewY] != ' ') return;
            if (pMasuKind == '*' && pBanArr[pNewX, pNewY] != '*') return;
            if (pMasuKind == 'B' && pBanArr[pNewX, pNewY] != ' ' && pBanArr[pNewX, pNewY] != '*') return;

            //訪問済なら再訪できない
            if (VisitedList.Exists(A => A.X == pNewX && A.Y == pNewY))
                return;
            VisitedList.Add(new PointDef() { X = pNewX, Y = pNewY });

            WillPush.X = pNewX;
            WillPush.Y = pNewY;
            stk.Push(WillPush);
        };

        foreach (PointDef EachStaPoint in pRinsetuBlackList) {
            PushSyori(EachStaPoint.X, EachStaPoint.Y);

            while (stk.Count > 0 && VisitedList.Count < pJyougen) {
                PointDef Popped = stk.Pop();

                if (Popped.X > 0) PushSyori(Popped.X - 1, Popped.Y); //左に移動
                if (Popped.X < UB_X) PushSyori(Popped.X + 1, Popped.Y); //右に移動
                if (Popped.Y > 0) PushSyori(Popped.X, Popped.Y - 1); //上に移動
                if (Popped.Y < UB_Y) PushSyori(Popped.X, Popped.Y + 1); //下に移動
            }
        }
        return VisitedList;
    }

    //解を出力
    static void PrintAnswer(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 CurrMasu = pBanArr[X, Y];
                if (CurrMasu == '/') sb.Append(' ');
                else sb.Append(CurrMasu);
            }
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }
}


実行結果

解を発見。経過時間=00:00:00.0451554
2**
03 ?
 * 1
 1 *


追加問題02 パズルBox12の05問目

char[,] Q02Arr = {{' ','2','1','2',' ','3',' ',' ',' ','1'},
                  {' ',' ',' ',' ',' ','3',' ',' ',' ','2'},
                  {' ',' ',' ','?',' ',' ',' ','1',' ',' '},
                  {'2',' ',' ','1',' ',' ','1',' ',' ',' '},
                  {'2',' ',' ',' ',' ','1',' ',' ',' ','5'},
                  {'2',' ',' ',' ','1',' ',' ',' ',' ','3'},
                  {' ',' ',' ','2',' ',' ','1',' ',' ','1'},
                  {' ',' ','1',' ',' ',' ','?',' ',' ',' '},
                  {'1',' ',' ',' ','?',' ',' ',' ',' ',' '},
                  {'?',' ',' ',' ','2',' ','1','2','1',' '}};


解説

UnionFindのアルゴリズムで、隣接したマスを列挙してます。