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

24-34 ひとりにしてくれ

問題

ニコリのひとりにしてくれを解きます。

例題と答え
    

1 盤面に並んでいる数字のうちいくつかを黒くぬって、
  タテでもヨコでも同じ列に同じ数字が複数個入らないようにしましょう。

2 黒マスをタテヨコに連続させたり、黒マスで盤面を分断したりしてはいけません。


ソース

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

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

    static char[,] mQuestionArr;
    static int UB_X;
    static int UB_Y;
    static char[] mNumArr;

    //問題を定義
    static void QuestionDef()
    {
        string[] Q01Arr ={"18626753",
                          "31118222",
                          "83247651",
                          "37583314",
                          "54467182",
                          "71432535",
                          "22834475",
                          "22314465"};

        string[] wkArr = Q01Arr;

        UB_X = wkArr[0].Length - 1;
        UB_Y = wkArr.GetUpperBound(0);

        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] = wkArr[Y][X];
            }
        }
        mNumArr = mQuestionArr.Cast<char>().Distinct().ToArray();
    }

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

        //黒マスがB、白マスがW、未確定が半角SPとなる2次元配列
        char[,] BlackWhiteArr = new char[UB_X + 1, UB_Y + 1];
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                BlackWhiteArr[X, Y] = ' ';
            }
        }

        //第1フェーズ 初回の確定探索
        SyokaiKakuteiTansaku(BlackWhiteArr);

        //第2フェーズ 確定探索
        ExecKakuteiTansaku(BlackWhiteArr);

        //第3フェーズ 確定探索付の深さ優先探索
        ExecDFS(BlackWhiteArr);
    }

    //第1フェーズ 初回の確定探索
    static void SyokaiKakuteiTansaku(char[,] pBlackWhiteArr)
    {
        //確定探索1 同じ数字に1マスだけ挟まれたマスは、白マスで確定
        KakuteiTansaku1(pBlackWhiteArr);

        //確定探索2 4隅とその周囲が同じ数字なら、4隅は黒マスで確定
        KakuteiTansaku2(pBlackWhiteArr);

        //確定探索3 □22□□2の場合は、右端の2が黒マスで確定
        KakuteiTansaku3(pBlackWhiteArr);
    }

    //確定探索1 同じ数字に1マスだけ挟まれたマスは、白マスで確定
    static void KakuteiTansaku1(char[,] pBlackWhiteArr)
    {
        //右方向のチェック
        for (int X = 0; X <= UB_X - 2; X++)
            for (int Y = 0; Y <= UB_Y; Y++)
                if (mQuestionArr[X, Y] == mQuestionArr[X + 2, Y])
                    SetWhite(pBlackWhiteArr, X + 1, Y);

        //下方向のチェック
        for (int X = 0; X <= UB_X; X++)
            for (int Y = 0; Y <= UB_Y - 2; Y++)
                if (mQuestionArr[X, Y] == mQuestionArr[X, Y + 2])
                    SetWhite(pBlackWhiteArr, X, Y + 1);
    }

    //確定探索2 4隅とその周囲が同じ数字なら、4隅は黒マスで確定
    static void KakuteiTansaku2(char[,] pBlackWhiteArr)
    {
        Action<int, int> SetAct = (pX, pY) =>
        {
            PointDef[] KinbouPointArr = DeriveKinbouPointArr(pX, pY);
            if (Array.TrueForAll(KinbouPointArr,
                A => mQuestionArr[A.X, A.Y] == mQuestionArr[pX, pY])) {
                SetBlack(pBlackWhiteArr, pX, pY);
            }
        };
        SetAct(0, 0);
        SetAct(0, UB_Y);
        SetAct(UB_X, 0);
        SetAct(UB_Y, UB_Y);
    }

    //確定探索3 □22□□2の場合は、右端の2が黒マスで確定
    static void KakuteiTansaku3(char[,] pBlackWhiteArr)
    {
        foreach (char EachNum in mNumArr) {
            //右方向のチェック
            for (int Y = 0; Y <= UB_Y; Y++) {
                var SeqPosSet = new HashSet<int>();
                for (int X = 0; X <= UB_X - 1; X++) {
                    if (mQuestionArr[X, Y] != EachNum) continue;
                    if (mQuestionArr[X, Y] == mQuestionArr[X + 1, Y]) {
                        SeqPosSet.Add(X);
                        SeqPosSet.Add(X + 1);
                        break;
                    }
                }
                if (SeqPosSet.Count == 0) continue;
                for (int X = 0; X <= UB_X; X++) {
                    if (mQuestionArr[X, Y] != EachNum) continue;
                    if (SeqPosSet.Contains(X)) continue;
                    SetBlack(pBlackWhiteArr, X, Y);
                }
            }

            //下方向のチェック
            for (int X = 0; X <= UB_X; X++) {
                var SeqPosSet = new HashSet<int>();
                for (int Y = 0; Y <= UB_Y - 1; Y++) {
                    if (mQuestionArr[X, Y] != EachNum) continue;
                    if (mQuestionArr[X, Y] == mQuestionArr[X, Y + 1]) {
                        SeqPosSet.Add(Y);
                        SeqPosSet.Add(Y + 1);
                        break;
                    }
                }
                if (SeqPosSet.Count == 0) continue;
                for (int Y = 0; Y <= UB_Y; Y++) {
                    if (mQuestionArr[X, Y] != EachNum) continue;
                    if (SeqPosSet.Contains(Y)) continue;
                    SetBlack(pBlackWhiteArr, X, Y);
                }
            }
        }
    }

    //第2フェーズ 確定探索
    static void ExecKakuteiTansaku(char[,] pBlackWhiteArr)
    {
        while (true) {
            char[,] PrevArr = (char[,])pBlackWhiteArr.Clone();

            //確定探索4 黒マスだと閉路ができるマスは、白マスで確定
            KakuteiTansaku4(pBlackWhiteArr);

            //確定探索5 白マスだと、上下左右に発生する黒マスで閉路ができる場合、黒マスで確定
            KakuteiTansaku5(pBlackWhiteArr);

            //確定探索で確定するマスがなくなったらBreak
            if (pBlackWhiteArr.Cast<char>().SequenceEqual(PrevArr.Cast<char>())) break;
        }
    }

    //確定探索4 黒マスだと閉路ができるマスは、白マスで確定
    static void KakuteiTansaku4(char[,] pBlackWhiteArr)
    {
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                if (pBlackWhiteArr[X, Y] != ' ') continue;

                char[,] CopiedArr = (char[,])pBlackWhiteArr.Clone();
                CopiedArr[X, Y] = 'B';

                if (HasBundan(CopiedArr)) {
                    SetWhite(pBlackWhiteArr, X, Y);
                }
            }
        }
    }

    //確定探索5 白マスだと、上下左右に発生する黒マスで閉路ができる場合、黒マスで確定
    static void KakuteiTansaku5(char[,] pBlackWhiteArr)
    {
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                if (pBlackWhiteArr[X, Y] != ' ') continue;

                //座標を引数として、上下左右で同じ数字の座標の配列を返す
                PointDef[] SameNumPointArr = DeriveSameNumPointArr(pBlackWhiteArr, X, Y);

                if (SameNumPointArr.Length == 0) continue;
                char[,] CopiedArr = (char[,])pBlackWhiteArr.Clone();

                SetWhite(CopiedArr, X, Y);
                if (HasBundan(CopiedArr)) {
                    SetBlack(pBlackWhiteArr, X, Y);
                }
            }
        }
    }

    //第3フェーズ 確定探索付の深さ優先探索
    static void ExecDFS(char[,] pBlackWhiteArr)
    {
        var Stk = new Stack<char[,]>();
        char[,] WillPushBanArr = pBlackWhiteArr;
        Stk.Push(WillPushBanArr);

        while (Stk.Count > 0) {
            char[,] PoppedBanArr = Stk.Pop();

            //クリア判定
            bool IsValid;
            if (IsClear(PoppedBanArr, out IsValid)) {
                Console.WriteLine("解を発見");
                PrintBan(PoppedBanArr, true);
                return;
            }
            if (IsValid == false) continue;

            bool WillBreak = false;
            for (int X = 0; X <= UB_X; X++) {
                for (int Y = 0; Y <= UB_Y; Y++) {
                    if (PoppedBanArr[X, Y] != ' ') continue;

                    Action<int, int> PushSyori = (pWhiteX, pWhiteY) =>
                    {
                        WillPushBanArr = (char[,])PoppedBanArr.Clone();
                        SetWhite(WillPushBanArr, pWhiteX, pWhiteY);
                        ExecKakuteiTansaku(WillPushBanArr);
                        Stk.Push(WillPushBanArr);
                    };

                    //座標を引数として、上下左右で同じ数字の座標の配列を返す
                    PointDef[] SameNumPointArr = DeriveSameNumPointArr(pBlackWhiteArr, X, Y);
                    SameNumPointArr =
                        Array.FindAll(SameNumPointArr, A => PoppedBanArr[A.X, A.Y] == ' ');
                    if (SameNumPointArr.Length == 0) continue;

                    PushSyori(X, Y);

                    //横方向で同じ数字の座標
                    PointDef[] YokoPointArr = Array.FindAll(SameNumPointArr, A => A.Y == Y);
                    if (YokoPointArr.Length > 0) {
                        Array.ForEach(YokoPointArr, A => PushSyori(A.X, A.Y));
                    }
                    else {
                        //縦方向で同じ数字の座標
                        Array.ForEach(SameNumPointArr, A => PushSyori(A.X, A.Y));
                    }
                    WillBreak = true;
                    break;
                }
                if (WillBreak) break;
            }
        }
    }

    //クリア判定
    static bool IsClear(char[,] pBlackWhiteArr, out bool pIsValid)
    {
        pIsValid = true;

        //条件1 上下左右に重複した数字がないこと
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                if (pBlackWhiteArr[X, Y] == 'B') continue;

                //座標を引数として、上下左右で同じ数字の座標の配列を返す
                PointDef[] SameNumPointArr = DeriveSameNumPointArr(pBlackWhiteArr, X, Y);
                if (SameNumPointArr.Length > 0) return false;
            }
        }

        //条件2 黒マスが連結していないこと
        //横の連結のチェック
        for (int X = 0; X <= UB_X - 1; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                if (pBlackWhiteArr[X, Y] == 'B' && pBlackWhiteArr[X + 1, Y] == 'B') {
                    pIsValid = false;
                    return false;
                }
            }
        }

        //縦の連結のチェック
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y - 1; Y++) {
                if (pBlackWhiteArr[X, Y] == 'B' && pBlackWhiteArr[X, Y + 1] == 'B') {
                    pIsValid = false;
                    return false;
                }
            }
        }

        //条件3 黒マスで分断されてないこと
        if (HasBundan(pBlackWhiteArr)) {
            pIsValid = false;
            return false;
        }

        return true;
    }

    struct JyoutaiDefUnionFind
    {
        internal int CurrX;
        internal int CurrY;
    }

    //黒マスで盤面が分断されてないかを判定
    static bool HasBundan(char[,] pBlackWhiteArr)
    {
        int StaX = -1, StaY = -1;
        bool WillBreak = false;

        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                if (pBlackWhiteArr[X, Y] != 'B') {
                    StaX = X;
                    StaY = Y;
                    WillBreak = true;
                    break;
                }
            }
            if (WillBreak) break;
        }

        var Stk = new Stack<JyoutaiDefUnionFind>();
        JyoutaiDefUnionFind WillPush;
        WillPush.CurrX = StaX;
        WillPush.CurrY = StaY;
        Stk.Push(WillPush);

        var VisitedSet = new HashSet<int>();
        VisitedSet.Add(StaX * (UB_Y + 1) + StaY);

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

            Action<int, int> PushSyori = (pNewX, pNewY) =>
            {
                if (pNewX < 0 || UB_X < pNewX) return;
                if (pNewY < 0 || UB_Y < pNewY) return;

                if (pBlackWhiteArr[pNewX, pNewY] == 'B') return;

                //再訪不可
                if (VisitedSet.Add(pNewX * (UB_Y + 1) + pNewY) == false)
                    return;

                WillPush.CurrX = pNewX;
                WillPush.CurrY = pNewY;
                Stk.Push(WillPush);
            };

            PushSyori(Popped.CurrX, Popped.CurrY - 1);
            PushSyori(Popped.CurrX, Popped.CurrY + 1);
            PushSyori(Popped.CurrX - 1, Popped.CurrY);
            PushSyori(Popped.CurrX + 1, Popped.CurrY);
        }
        int NeedCnt = pBlackWhiteArr.Cast<char>().Count(A => A != 'B');
        return VisitedSet.Count < NeedCnt;
    }

    //黒マスを設置し、4近傍を白マスにする
    static void SetBlack(char[,] pBlackWhiteArr, int pX, int pY)
    {
        if (pBlackWhiteArr[pX, pY] != ' ') return;
        pBlackWhiteArr[pX, pY] = 'B';

        PointDef[] KinbouPointArr = DeriveKinbouPointArr(pX, pY);
        Array.ForEach(KinbouPointArr, A => SetWhite(pBlackWhiteArr, A.X, A.Y));
    }

    //白マスを設置し、上下左右に同じ数字があったら、黒マスにする
    static void SetWhite(char[,] pBlackWhiteArr, int pX, int pY)
    {
        if (pBlackWhiteArr[pX, pY] != ' ') return;
        pBlackWhiteArr[pX, pY] = 'W';

        PointDef[] SameNumPointArr = DeriveSameNumPointArr(pBlackWhiteArr, pX, pY);
        Array.ForEach(SameNumPointArr, A => SetBlack(pBlackWhiteArr, A.X, A.Y));
    }

    //4近傍の座標の配列を返す
    static PointDef[] DeriveKinbouPointArr(int pBaseX, int pBaseY)
    {
        var WillReturnList = new List<PointDef>();

        Action<int, int> AddAct = (pX, pY) =>
        {
            if (pX < 0 || UB_X < pX) return;
            if (pY < 0 || UB_Y < pY) return;

            WillReturnList.Add(new PointDef() { X = pX, Y = pY });
        };
        AddAct(pBaseX, pBaseY - 1);
        AddAct(pBaseX, pBaseY + 1);
        AddAct(pBaseX - 1, pBaseY);
        AddAct(pBaseX + 1, pBaseY);
        return WillReturnList.ToArray();
    }

    //座標を引数として、上下左右で同じ数字の座標の配列を返す
    static PointDef[] DeriveSameNumPointArr(char[,] pBlackWhiteArr, int pBaseX, int pBaseY)
    {
        var WillReturn = new List<PointDef>();

        char CurrNum = mQuestionArr[pBaseX, pBaseY];

        Action<int, int> AddAct = (pHeniX, pHeniY) =>
        {
            int LoopX = pBaseX;
            int LoopY = pBaseY;

            while (true) {
                LoopX += pHeniX;
                LoopY += pHeniY;

                if (LoopX < 0 || UB_X < LoopX) break;
                if (LoopY < 0 || UB_Y < LoopY) break;

                if (pBlackWhiteArr[LoopX, LoopY] == 'B') continue;

                if (mQuestionArr[LoopX, LoopY] == CurrNum) {
                    WillReturn.Add(new PointDef() { X = LoopX, Y = LoopY });
                }
            }
        };
        AddAct(0, -1);
        AddAct(0, 1);
        AddAct(-1, 0);
        AddAct(1, 0);
        return WillReturn.ToArray();
    }

    //盤面を出力
    static void PrintBan(char[,] pBlackWhiteArr, bool pIsAnswer)
    {
        var sb = new System.Text.StringBuilder();
        for (int Y = 0; Y <= UB_Y; Y++) {
            for (int X = 0; X <= UB_X; X++) {
                char CurrChar = pBlackWhiteArr[X, Y];
                if (CurrChar == 'B') sb.Append('■');
                else if (CurrChar == 'W' && pIsAnswer == false) sb.Append('○');
                else sb.Append(mQuestionArr[X, Y]);
            }
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }
}


実行結果

解を発見
18■26753
3■1■8■2■
832476■1
■7583■14
54■6■182
714■253■
2■834■75
■231■46■


追加問題02 パズルBox10の例題

string[] Q02Arr ={"1145763",
                  "1333255",
                  "3462156",
                  "5617427",
                  "5741342",
                  "2276631",
                  "2256647"};


追加問題03 パズルBox10の06問目

string[] Q03Arr ={"56381624",
                  "74111273",
                  "63187345",
                  "27435816",
                  "16882454",
                  "21243738",
                  "36784585",
                  "28465171"};


追加問題04 パズルBox10の08問目

string[] Q04Arr ={"HCH7A349264F18DE4",
                  "3182C1D6EAG24696F",
                  "H1H26A4F391B1G7C3",
                  "B1G2D17AHA8294436",
                  "GB96GDGHG2341AA81",
                  "7934H1EA6AF2D5GF8",
                  "174A565298E33HEGD",
                  "A424719BGBD6638DH",
                  "12452G379FC88DCAE",
                  "23F3816C4CBAA75B9",
                  "587G2B349E61FAAH1",
                  "E65331BDAD91G4472",
                  "5A7E49438H5G5B215",
                  "D5E894A5F5H57212B",
                  "6G6H25389167BE64A",
                  "86A1B4H15G7DE232C",
                  "6F7D283E14999CB59"};


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

string[] Q05Arr ={"AEBFCGDH8287B6C5D",
                  "48EBFBGDH1297B6C5",
                  "A4AEAFAG9192A7B6C",
                  "5888E111G1H92A7A6",
                  "95A4BECF9GDH8287D",
                  "6A5A4AEDFAGBHB2B7",
                  "B6C5D4AEBFBGDHA2A",
                  "796B584DEAFCGCH82",
                  "C7A6D5A4CECFDGAHA",
                  "3A7B685D4AEDFDGDH",
                  "D3D7D6A5D4DE9FBGC",
                  "HB3C7D6D5A4BECFDG",
                  "9HA3B7C615949EAFA",
                  "GAHA3A716A5A49EDF",
                  "AGCHD39186B5A49E8",
                  "FDGDHD317D69594AE",
                  "CFDG8H9387B6A5D4B"};


解説

確定探索付の深さ優先探索で解いてます。