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

24-20 スリザーリンク

問題

ニコリのスリザーリンクを解きます。

例題と答え
    

1 点と点の間にタテヨコに線を引き、全体で1つの輪っかを作りましょう。

2 4つの点で作られた正方形の中にある数字は、その正方形の辺に引く線の数を表しています。
  数字のない正方形には、何本の線を引くかわかりません。

3 線を交差させたり、枝分かれさせたりしてはいけません。


ソース

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

class Program
{
    //4方向のビット定義
    const int Bit0 = 1; //上方向
    const int Bit1 = 2; //右方向
    const int Bit2 = 4; //下方向
    const int Bit3 = 8; //左方向
    const int BitAllOn = Bit0 | Bit1 | Bit2 | Bit3;

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

    //数値情報の構造体
    struct NumInfoDef
    {
        internal int NumVal; //数値
        internal PointDef Point; //座標
    }

    static char[,] QuestionArr;
    static int UB_X;
    static int UB_Y;

    static NumInfoDef[] NumInfoArr; //数値情報の配列

    //解となる、内側の島の座標
    static List<PointDef> AnswerUtigawaShimaList = new List<PointDef>();

    static System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();

    static void Main()
    {
        char[,] Q01Arr = {{' ','0',' ','1',' ',' ','1',' '},
                          {' ','3',' ',' ','2','3',' ','2'},
                          {' ',' ','0',' ',' ',' ',' ','0'},
                          {' ','3',' ',' ','0',' ',' ',' '},
                          {' ',' ',' ','3',' ',' ','0',' '},
                          {'1',' ',' ',' ',' ','3',' ',' '},
                          {'3',' ','1','3',' ',' ','3',' '},
                          {' ','0',' ',' ','3',' ','3',' '}};

        QuestionArr = XYRev(Q01Arr);
        UB_X = QuestionArr.GetUpperBound(0);
        UB_Y = QuestionArr.GetUpperBound(1);

        //第1フェーズ 数値情報を作成する
        NumInfoArr = DeriveNumInfoArr();

        //デバッグ用の数値情報の出力
        //DebugPrintNumInfo();

        //第2フェーズ 0のマス目の周りにバツ印を付ける
        int[,] BatuArr = WriteBatuAroundZero();

        //第3フェーズ 確定探索
        int[,] ArrowArr = new int[UB_X + 1, UB_Y + 1];
        ExecKakuteiTansaku(ArrowArr, BatuArr, true);

        //Console.WriteLine("第3フェーズ 確定探索後の線の配置");
        //PrintBitValueArr(ArrowArr);

        //Console.WriteLine("第3フェーズ 確定探索後のバツ印の配置");
        //PrintBitValueArr(BatuArr);

        //第4フェーズ 深さ優先探索
        ExecDFS(ArrowArr, BatuArr);
    }

    //X座標とY座標の入れ替え
    static char[,] XYRev(char[,] pBaseArr)
    {
        int RevArrUB_X = pBaseArr.GetUpperBound(1);
        int RevArrUB_Y = pBaseArr.GetUpperBound(0);
        char[,] WillReturnArr = new char[RevArrUB_X + 1, RevArrUB_Y + 1];
        for (int X = 0; X <= RevArrUB_X; X++) {
            for (int Y = 0; Y <= RevArrUB_Y; Y++) {
                WillReturnArr[X, Y] = pBaseArr[Y, X];
            }
        }
        return WillReturnArr;
    }

    //第1フェーズ 数値情報を作成する
    static NumInfoDef[] DeriveNumInfoArr()
    {
        var WillReturnList = new List<NumInfoDef>();

        for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
            for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                if (QuestionArr[LoopX, LoopY] == ' ')
                    continue;

                NumInfoDef WillAdd;
                WillAdd.NumVal = StrToDec(QuestionArr[LoopX, LoopY]);
                WillAdd.Point = new PointDef() { X = LoopX, Y = LoopY };
                WillReturnList.Add(WillAdd);
            }
        }
        return WillReturnList.ToArray();
    }

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

    //デバッグ用の数値情報の出力
    static void DebugPrintNumInfo()
    {
        for (int I = 0; I <= NumInfoArr.GetUpperBound(0); I++) {
            Console.WriteLine("■■■■■■■■■■■■■");
            Console.WriteLine("{0}個目の数値情報", I + 1);
            Console.WriteLine("NumVal={0}", NumInfoArr[I].NumVal);
            Console.WriteLine("(X,Y)=({0},{1})", NumInfoArr[I].Point.X, NumInfoArr[I].Point.Y);
        }
    }

    //ビット演算の結果が0より大きいかを返す
    static bool IsBitTrue(int pInt)
    {
        return pInt > 0;
    }

    //ビット演算の結果が0かを返す
    static bool IsBitFalse(int pInt)
    {
        return pInt == 0;
    }

    //第2フェーズ 0のマス目の周りにバツ印を付ける
    static int[,] WriteBatuAroundZero()
    {
        int[,] BatuArr = new int[UB_X + 1, UB_Y + 1];

        foreach (NumInfoDef EachNumInfo in NumInfoArr) {
            if (EachNumInfo.NumVal != 0) continue;

            int X = EachNumInfo.Point.X;
            int Y = EachNumInfo.Point.Y;
            TargetPointBitOn(X, Y, BitAllOn, BatuArr);
        }
        return BatuArr;
    }

    //指定座標の指定ビットを立てる、指定座標の隣接座標のビットも変更する
    static void TargetPointBitOn(int pX, int pY, int pBit, int[,] pTargetArr)
    {
        if (pX < 0 || UB_X < pX) return;
        if (pY < 0 || UB_Y < pY) return;

        pTargetArr[pX, pY] |= pBit;

        //上の座標のビットを立てる
        if (IsBitTrue(pBit & Bit0) && pY - 1 >= 0) pTargetArr[pX, pY - 1] |= Bit2;
        //右の座標のビットを立てる
        if (IsBitTrue(pBit & Bit1) && pX + 1 <= UB_X) pTargetArr[pX + 1, pY] |= Bit3;
        //下の座標のビットを立てる
        if (IsBitTrue(pBit & Bit2) && pY + 1 <= UB_Y) pTargetArr[pX, pY + 1] |= Bit0;
        //左の座標のビットを立てる
        if (IsBitTrue(pBit & Bit3) && pX - 1 >= 0) pTargetArr[pX - 1, pY] |= Bit1;
    }

    //第3フェーズ 確定探索
    static void ExecKakuteiTansaku(int[,] pArrowArr, int[,] pBatuArr, bool WillCallBatuKakuteiTansaku5)
    {
        bool FirstFlag = true;//1回だけ行う確定探索の制御用

        //確定探索を行うAct
        Action<int, int> KakuteiTansakuAct = (pX, pY) =>
        {
            //線を引く確定探索1 数値と比較して、引ける線が一意に決まる場合
            ArrowKakuteiTansaku1(pX, pY, pArrowArr, pBatuArr);

            //線を引く確定探索2 数値の3の8近傍に数値の3がある場合
            if (FirstFlag) ArrowKakuteiTansaku2(pX, pY, pArrowArr);

            //線を引く確定探索3 線の方向が一意に決まる場合
            ArrowKakuteiTansaku3(pX, pY, pArrowArr, pBatuArr);

            //線を引く確定探索4 数値の3の頂点に線が引かれている場合
            ArrowKakuteiTansaku4(pX, pY, pArrowArr);

            //バツ印を付ける確定探索1 既に数値の線を引いている場合
            BatuKakuteiTansaku1(pX, pY, pArrowArr, pBatuArr);

            //バツ印を付ける確定探索2 線を3本引いている場合
            BatuKakuteiTansaku2(pX, pY, pArrowArr, pBatuArr);

            //バツ印を付ける確定探索3 枝分かれが発生するので引けない場合
            BatuKakuteiTansaku3(pX, pY, pArrowArr, pBatuArr);

            //バツ印を付ける確定探索4 線を引くと、行き先が全てバツ印になる場合
            BatuKakuteiTansaku4(pX, pY, pArrowArr, pBatuArr);

            //バツ印を付ける確定探索6 数値の1の頂点に線とバツが隣接している場合
            BatuKakuteiTansaku6(pX, pY, pArrowArr, pBatuArr);

            //数値確定探索1 数値が1か3で頂点にバツ印2つが隣接している場合
            SuutiKakuteiTansaku1(pX, pY, pArrowArr, pBatuArr);

            //数値確定探索2 数値が2で頂点にバツ印2つが隣接している場合
            SuutiKakuteiTansaku2(pX, pY, pArrowArr, pBatuArr);
        };

        while (true) {
            int[,] PrevArrowArr = (int[,])pArrowArr.Clone();
            int[,] PrevBatuArr = (int[,])pBatuArr.Clone();

            for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                    KakuteiTansakuAct(LoopX, LoopY);
                }
            }

            //確定探索で確定するマスがあったらContine
            if (pArrowArr.Cast<int>().SequenceEqual(PrevArrowArr.Cast<int>()) == false) continue;
            if (pBatuArr.Cast<int>().SequenceEqual(PrevBatuArr.Cast<int>()) == false) continue;

            //バツ印を付ける確定探索5 線を引くと、孤立した島ができる場合
            if (WillCallBatuKakuteiTansaku5) {
                for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                    for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                        BatuKakuteiTansaku5(LoopX, LoopY, pArrowArr, pBatuArr);
                    }
                }
            }

            //確定探索で確定するマスがなくなったらBreak
            if (pArrowArr.Cast<int>().SequenceEqual(PrevArrowArr.Cast<int>()) == false) continue;
            if (pBatuArr.Cast<int>().SequenceEqual(PrevBatuArr.Cast<int>()) == false) continue;
            break;
        }
    }

    //線を引く確定探索1 数値と比較して、引ける線が一意に決まる場合
    static void ArrowKakuteiTansaku1(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        int wkInd = Array.FindIndex(NumInfoArr, A => A.Point.X == pX && A.Point.Y == pY);
        if (wkInd == -1) return; //数値マスでない場合
        int wkNumVal = NumInfoArr[wkInd].NumVal;

        //バツの数が不足している場合
        int BatuCnt = DeriveTargetPointBitCnt(pX, pY, pBatuArr);
        if (BatuCnt < 4 - wkNumVal) return;

        //既に数値分の線が引かれている場合
        int ArrowCnt = DeriveTargetPointBitCnt(pX, pY, pArrowArr);
        if (wkNumVal == ArrowCnt) return;

        //バツでない箇所は、全て線で確定
        if (IsBitFalse(pBatuArr[pX, pY] & Bit0)) TargetPointBitOn(pX, pY, Bit0, pArrowArr);
        if (IsBitFalse(pBatuArr[pX, pY] & Bit1)) TargetPointBitOn(pX, pY, Bit1, pArrowArr);
        if (IsBitFalse(pBatuArr[pX, pY] & Bit2)) TargetPointBitOn(pX, pY, Bit2, pArrowArr);
        if (IsBitFalse(pBatuArr[pX, pY] & Bit3)) TargetPointBitOn(pX, pY, Bit3, pArrowArr);
    }

    //指定座標に立っている、ビットの数を返す
    static int DeriveTargetPointBitCnt(int pX, int pY, int[,] pTargetArr)
    {
        int WillReturnCnt = 0;
        int CurrVal = pTargetArr[pX, pY];
        WillReturnCnt += Math.Sign(CurrVal & Bit0);
        WillReturnCnt += Math.Sign(CurrVal & Bit1);
        WillReturnCnt += Math.Sign(CurrVal & Bit2);
        WillReturnCnt += Math.Sign(CurrVal & Bit3);
        return WillReturnCnt;
    }

    //線を引く確定探索2 数値の3の8近傍に数値の3がある場合
    static void ArrowKakuteiTansaku2(int pX, int pY, int[,] pArrowArr)
    {
        int wkInd1 = Array.FindIndex(NumInfoArr, A => A.Point.X == pX && A.Point.Y == pY);
        if (wkInd1 == -1) return; //数値マスでない場合
        int wkNumVal = NumInfoArr[wkInd1].NumVal;
        if (wkNumVal != 3) return; //数値の3でない場合

        NumInfoDef[] NumInfo3Only = Array.FindAll(NumInfoArr, A => A.NumVal == 3);

        //Order By X座標、Y座標で、後続の座標のみをチェックする

        //3の右に3があるケース
        bool HasMigi = Array.Exists(NumInfo3Only, A => A.Point.X == pX + 1 && A.Point.Y == pY);
        //3の左下に3があるケース
        bool HasHidariShita = Array.Exists(NumInfo3Only, A => A.Point.X == pX - 1 && A.Point.Y == pY + 1);
        //3の下に3があるケース
        bool HasShita = Array.Exists(NumInfo3Only, A => A.Point.X == pX && A.Point.Y == pY + 1);
        //3の右下に3があるケース
        bool HasMigiShita = Array.Exists(NumInfo3Only, A => A.Point.X == pX + 1 && A.Point.Y == pY + 1);

        if (HasMigi) { //3の右に3があるケース
            TargetPointBitOn(pX, pY, Bit1 | Bit3, pArrowArr);
            TargetPointBitOn(pX + 1, pY, Bit1 | Bit3, pArrowArr);
        }
        if (HasHidariShita) { //3の左下に3があるケース
            TargetPointBitOn(pX, pY, Bit0 | Bit1, pArrowArr);
            TargetPointBitOn(pX - 1, pY + 1, Bit2 | Bit3, pArrowArr);
        }
        if (HasShita) { //3の下に3があるケース
            TargetPointBitOn(pX, pY, Bit0 | Bit2, pArrowArr);
            TargetPointBitOn(pX, pY + 1, Bit0 | Bit2, pArrowArr);
        }
        if (HasMigiShita) { //3の右下に3があるケース
            TargetPointBitOn(pX, pY, Bit0 | Bit3, pArrowArr);
            TargetPointBitOn(pX + 1, pY + 1, Bit1 | Bit2, pArrowArr);
        }
    }

    //線を引く確定探索3 線の方向が一意に決まる場合
    static void ArrowKakuteiTansaku3(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        //両端で、バツ印が2つと、未確定マスが1マスなら、線で確定する

        //指定座標の上の線から、つなげる場合
        if (IsBitTrue(pArrowArr[pX, pY] & Bit0)) {
            //左端からつなげる場合
            CheckUniqLine(RinsetuCase01_Bit_UeLine_Hidari(pX, pY), pArrowArr, pBatuArr);

            //右端からつなげる場合
            CheckUniqLine(RinsetuCase02_Bit_UeLine_Migi(pX, pY), pArrowArr, pBatuArr);
        }
        //指定座標の右の線から、つなげる場合
        if (IsBitTrue(pArrowArr[pX, pY] & Bit1)) {
            //上端からつなげる場合
            CheckUniqLine(RinsetuCase03_Bit_MigiLine_Ue(pX, pY), pArrowArr, pBatuArr);

            //下端からつなげる場合
            CheckUniqLine(RinsetuCase04_Bit_MigiLine_Shita(pX, pY), pArrowArr, pBatuArr);
        }
        //指定座標の下の線から、つなげる場合
        if (IsBitTrue(pArrowArr[pX, pY] & Bit2)) {
            //左端からつなげる場合
            CheckUniqLine(RinsetuCase05_Bit_ShitaLine_Hidari(pX, pY), pArrowArr, pBatuArr);

            //右端からつなげる場合
            CheckUniqLine(RinsetuCase06_Bit_ShitaLine_Migi(pX, pY), pArrowArr, pBatuArr);
        }
        //指定座標の左の線から、つなげる場合
        if (IsBitTrue(pArrowArr[pX, pY] & Bit3)) {
            //上端からつなげる場合
            CheckUniqLine(RinsetuCase07_Bit_HidariLine_Ue(pX, pY), pArrowArr, pBatuArr);

            //下端からつなげる場合
            CheckUniqLine(RinsetuCase08_Bit_HidariLine_Shita(pX, pY), pArrowArr, pBatuArr);
        }
    }

    //隣接した線の情報を持つ構造体
    struct RinsetuBitDef
    {
        internal int X;
        internal int Y;
        internal int Bit;
    }

    //指定座標の上の線の左端に、隣接した線の情報をListで返す
    static List<RinsetuBitDef> RinsetuCase01_Bit_UeLine_Hidari(int pX, int pY)
    {
        var WillRetunList = new List<RinsetuBitDef>();
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY - 1, Bit = Bit3 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX - 1, Y = pY, Bit = Bit0 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY, Bit = Bit3 });
        return WillRetunList;
    }

    //指定座標の上の線の右端に、隣接した線の情報をListで返す
    static List<RinsetuBitDef> RinsetuCase02_Bit_UeLine_Migi(int pX, int pY)
    {
        var WillRetunList = new List<RinsetuBitDef>();
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY - 1, Bit = Bit1 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX + 1, Y = pY, Bit = Bit0 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY, Bit = Bit1 });
        return WillRetunList;
    }

    //指定座標の右の線の上端に、隣接した線の情報をListで返す
    static List<RinsetuBitDef> RinsetuCase03_Bit_MigiLine_Ue(int pX, int pY)
    {
        var WillRetunList = new List<RinsetuBitDef>();
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY - 1, Bit = Bit1 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY, Bit = Bit0 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX + 1, Y = pY, Bit = Bit0 });
        return WillRetunList;
    }

    //指定座標の右の線の下端に、隣接した線の情報をListで返す
    static List<RinsetuBitDef> RinsetuCase04_Bit_MigiLine_Shita(int pX, int pY)
    {
        var WillRetunList = new List<RinsetuBitDef>();
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY + 1, Bit = Bit1 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY, Bit = Bit2 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX + 1, Y = pY, Bit = Bit2 });
        return WillRetunList;
    }

    //指定座標の下の線の左端に、隣接した線の情報をListで返す
    static List<RinsetuBitDef> RinsetuCase05_Bit_ShitaLine_Hidari(int pX, int pY)
    {
        var WillRetunList = new List<RinsetuBitDef>();
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY, Bit = Bit3 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX - 1, Y = pY, Bit = Bit2 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY + 1, Bit = Bit3 });
        return WillRetunList;
    }

    //指定座標の下の線の右端に、隣接した線の情報をListで返す
    static List<RinsetuBitDef> RinsetuCase06_Bit_ShitaLine_Migi(int pX, int pY)
    {
        var WillRetunList = new List<RinsetuBitDef>();
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY, Bit = Bit1 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX + 1, Y = pY, Bit = Bit2 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY + 1, Bit = Bit1 });
        return WillRetunList;
    }

    //指定座標の左の線の上端に、隣接した線の情報をListで返す
    static List<RinsetuBitDef> RinsetuCase07_Bit_HidariLine_Ue(int pX, int pY)
    {
        var WillRetunList = new List<RinsetuBitDef>();
        WillRetunList.Add(new RinsetuBitDef() { X = pX - 1, Y = pY, Bit = Bit0 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY - 1, Bit = Bit3 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY, Bit = Bit0 });
        return WillRetunList;
    }

    //指定座標の左の線の下端に、隣接した線の情報をListで返す
    static List<RinsetuBitDef> RinsetuCase08_Bit_HidariLine_Shita(int pX, int pY)
    {
        var WillRetunList = new List<RinsetuBitDef>();
        WillRetunList.Add(new RinsetuBitDef() { X = pX - 1, Y = pY, Bit = Bit2 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY, Bit = Bit2 });
        WillRetunList.Add(new RinsetuBitDef() { X = pX, Y = pY + 1, Bit = Bit3 });
        return WillRetunList;
    }

    //線の方向が一意に決まるなら、線を引く
    static void CheckUniqLine(List<RinsetuBitDef> pRinsetuBitList, int[,] pArrowArr, int[,] pBatuArr)
    {
        //バツ印のビットかを判定
        Predicate<RinsetuBitDef> IsBatuPointPred = pRinsetuBit =>
        {
            PointDef wkPoint = new PointDef() { X = pRinsetuBit.X, Y = pRinsetuBit.Y };
            return IsBatuPoint(wkPoint, pRinsetuBit.Bit, pBatuArr);
        };

        //バツ印で線が引けない要素をRemoveした結果が、未確定の線だけなら、線で確定
        pRinsetuBitList.RemoveAll(A => IsBatuPointPred(A));
        if (pRinsetuBitList.Count != 1) return;

        int RestPointX = pRinsetuBitList[0].X;
        int RestPointY = pRinsetuBitList[0].Y;
        int RestOneBit = pRinsetuBitList[0].Bit;
        PointDef RestOnePoint = new PointDef() { X = RestPointX, Y = RestPointY };
        if (IsMikakuteiPoint(RestOnePoint, RestOneBit, pArrowArr, pBatuArr)) {
            TargetPointBitOn(RestPointX, RestPointY, RestOneBit, pArrowArr);
        }
    }

    //指定座標の指定ビットが未確定かを返す
    //指定座標が範囲外ならFalseを返す
    static bool IsMikakuteiPoint(PointDef pPoint, int pBit, int[,] pArrowArr, int[,] pBatuArr)
    {
        int CurrX = pPoint.X, CurrY = pPoint.Y;
        if (CurrX < 0 || UB_X < CurrX) return false;
        if (CurrY < 0 || UB_Y < CurrY) return false;

        if (IsBitTrue(pArrowArr[CurrX, CurrY] & pBit)) return false;
        if (IsBitTrue(pBatuArr[CurrX, CurrY] & pBit)) return false;
        return true;
    }

    //指定座標の指定ビットが線かを判定する
    //指定座標が範囲外なら線でないとみなす
    static bool IsArrowPoint(PointDef pPoint, int pBit, int[,] pArrowArr)
    {
        int CurrX = pPoint.X, CurrY = pPoint.Y;
        if (CurrX < 0 || UB_X < CurrX) return false;
        if (CurrY < 0 || UB_Y < CurrY) return false;

        return IsBitTrue(pArrowArr[CurrX, CurrY] & pBit);
    }

    //指定座標の指定ビットがバツ印かを判定する
    //指定座標が範囲外ならバツ印とみなす
    static bool IsBatuPoint(PointDef pPoint, int pBit, int[,] pBatuArr)
    {
        int CurrX = pPoint.X, CurrY = pPoint.Y;
        if (CurrX < 0 || UB_X < CurrX) return true;
        if (CurrY < 0 || UB_Y < CurrY) return true;

        return IsBitTrue(pBatuArr[CurrX, CurrY] & pBit);
    }

    //線を引く確定探索4 数値の3の頂点に線が引かれている場合
    static void ArrowKakuteiTansaku4(int pX, int pY, int[,] pArrowArr)
    {
        int wkInd = Array.FindIndex(NumInfoArr, A => A.Point.X == pX && A.Point.Y == pY);
        if (wkInd == -1) return; //数値マスでない場合
        int CurrNumVal = NumInfoArr[wkInd].NumVal;

        //3でない場合
        if (CurrNumVal != 3) return;

        List<RinsetuBitDef> wkRinsetuBitList = null;

        Action<int, int> KakuteiAct = (pKakuteiBit1, pKakuteiBit2) =>
        {
            //自分の座標はRemove
            wkRinsetuBitList.RemoveAll(A => A.X == pX && A.Y == pY);

            //頂点に線が引かれているかをチェック
            if (wkRinsetuBitList.Exists(A => IsArrowPoint(new PointDef() { X = A.X, Y = A.Y },
                                                          A.Bit, pArrowArr)) == false)
                return;

            //線の確定処理
            TargetPointBitOn(pX, pY, pKakuteiBit1, pArrowArr);
            TargetPointBitOn(pX, pY, pKakuteiBit2, pArrowArr);
        };

        //指定座標の上の線の、左端をチェック
        wkRinsetuBitList = RinsetuCase01_Bit_UeLine_Hidari(pX, pY);
        KakuteiAct(Bit1, Bit2);

        //指定座標の上の線の、右端をチェック
        wkRinsetuBitList = RinsetuCase02_Bit_UeLine_Migi(pX, pY);
        KakuteiAct(Bit2, Bit3);

        //指定座標の下の線の、左端をチェック
        wkRinsetuBitList = RinsetuCase05_Bit_ShitaLine_Hidari(pX, pY);
        KakuteiAct(Bit0, Bit1);

        //指定座標の下の線の、右端をチェック
        wkRinsetuBitList = RinsetuCase06_Bit_ShitaLine_Migi(pX, pY);
        KakuteiAct(Bit0, Bit3);
    }

    //バツ印を付ける確定探索1 既に数値の線を引いている場合
    static void BatuKakuteiTansaku1(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        int wkInd = Array.FindIndex(NumInfoArr, A => A.Point.X == pX && A.Point.Y == pY);
        if (wkInd == -1) return; //数値マスでない場合
        int wkNumVal = NumInfoArr[wkInd].NumVal;

        //線の数が不足している場合
        int ArrowCnt = DeriveTargetPointBitCnt(pX, pY, pArrowArr);
        if (ArrowCnt < wkNumVal) return;

        //バツが必要数に到達している場合
        int BatuCnt = DeriveTargetPointBitCnt(pX, pY, pBatuArr);
        if (BatuCnt == 4 - wkNumVal) return;

        //線でない箇所は、全てバツで確定
        if (IsBitFalse(pArrowArr[pX, pY] & Bit0)) TargetPointBitOn(pX, pY, Bit0, pBatuArr);
        if (IsBitFalse(pArrowArr[pX, pY] & Bit1)) TargetPointBitOn(pX, pY, Bit1, pBatuArr);
        if (IsBitFalse(pArrowArr[pX, pY] & Bit2)) TargetPointBitOn(pX, pY, Bit2, pBatuArr);
        if (IsBitFalse(pArrowArr[pX, pY] & Bit3)) TargetPointBitOn(pX, pY, Bit3, pBatuArr);
    }

    //バツ印を付ける確定探索2 線を3本引いている場合
    static void BatuKakuteiTansaku2(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        int ArrowCnt = DeriveTargetPointBitCnt(pX, pY, pArrowArr);
        if (ArrowCnt < 3) return;

        //線でない箇所がバツで確定
        if (IsBitFalse(pArrowArr[pX, pY] & Bit0)) TargetPointBitOn(pX, pY, Bit0, pBatuArr);
        if (IsBitFalse(pArrowArr[pX, pY] & Bit1)) TargetPointBitOn(pX, pY, Bit1, pBatuArr);
        if (IsBitFalse(pArrowArr[pX, pY] & Bit2)) TargetPointBitOn(pX, pY, Bit2, pBatuArr);
        if (IsBitFalse(pArrowArr[pX, pY] & Bit3)) TargetPointBitOn(pX, pY, Bit3, pBatuArr);
    }

    //バツ印を付ける確定探索3 枝分かれが発生するので引けない場合
    static void BatuKakuteiTansaku3(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        if (pX + 1 <= UB_X) { //右にマスが存在する場合
            //ケース1
            //11
            if (IsBitTrue(pArrowArr[pX, pY] & Bit0)
             && IsBitTrue(pArrowArr[pX + 1, pY] & Bit0)) {
                TargetPointBitOn(pX, pY, Bit1, pBatuArr);
                TargetPointBitOn(pX, pY - 1, Bit1, pBatuArr);
            }
            //ケース2
            //44
            if (IsBitTrue(pArrowArr[pX, pY] & Bit2)
             && IsBitTrue(pArrowArr[pX + 1, pY] & Bit2)) {
                TargetPointBitOn(pX, pY, Bit1, pBatuArr);
                TargetPointBitOn(pX, pY + 1, Bit1, pBatuArr);
            }
        }
        if (pY + 1 <= UB_Y) { //下にマスが存在する場合
            //ケース3
            //2
            //2
            if (IsBitTrue(pArrowArr[pX, pY] & Bit1)
             && IsBitTrue(pArrowArr[pX, pY + 1] & Bit1)) {
                TargetPointBitOn(pX, pY, Bit2, pBatuArr);
                TargetPointBitOn(pX + 1, pY, Bit2, pBatuArr);
            }
            //ケース4
            //8
            //8
            if (IsBitTrue(pArrowArr[pX, pY] & Bit3)
             && IsBitTrue(pArrowArr[pX, pY + 1] & Bit3)) {
                TargetPointBitOn(pX, pY, Bit2, pBatuArr);
                TargetPointBitOn(pX - 1, pY, Bit2, pBatuArr);
            }
        }

        //ケース5
        //3
        if (IsBitTrue(pArrowArr[pX, pY] & Bit0)
         && IsBitTrue(pArrowArr[pX, pY] & Bit1)) {
            TargetPointBitOn(pX, pY - 1, Bit1, pBatuArr);
            TargetPointBitOn(pX + 1, pY, Bit0, pBatuArr);
        }

        //ケース6
        //6
        if (IsBitTrue(pArrowArr[pX, pY] & Bit1)
         && IsBitTrue(pArrowArr[pX, pY] & Bit2)) {
            TargetPointBitOn(pX + 1, pY, Bit2, pBatuArr);
            TargetPointBitOn(pX, pY + 1, Bit1, pBatuArr);
        }

        //ケース6
        //C
        if (IsBitTrue(pArrowArr[pX, pY] & Bit2)
         && IsBitTrue(pArrowArr[pX, pY] & Bit3)) {
            TargetPointBitOn(pX - 1, pY, Bit2, pBatuArr);
            TargetPointBitOn(pX, pY + 1, Bit3, pBatuArr);
        }

        //ケース7
        //9
        if (IsBitTrue(pArrowArr[pX, pY] & Bit3)
         && IsBitTrue(pArrowArr[pX, pY] & Bit0)) {
            TargetPointBitOn(pX, pY - 1, Bit3, pBatuArr);
            TargetPointBitOn(pX - 1, pY, Bit0, pBatuArr);
        }
    }

    //バツ印を付ける確定探索4 線を引くと、行き先が全てバツ印になる場合
    static void BatuKakuteiTansaku4(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        //両端で、少なくとも片方の方向が全てバツ印なら、線は引けないのでバツ印で確定する

        List<RinsetuBitDef> wkRinsetuBitList;

        //指定座標の上が未確定の場合
        if (IsBitFalse(pArrowArr[pX, pY] & Bit0) && IsBitFalse(pBatuArr[pX, pY] & Bit0)) {
            //両端のうちの、左方向をチェック
            wkRinsetuBitList = RinsetuCase01_Bit_UeLine_Hidari(pX, pY);
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)))
                TargetPointBitOn(pX, pY, Bit0, pBatuArr);

            //両端のうちの、右方向をチェック
            wkRinsetuBitList = RinsetuCase02_Bit_UeLine_Migi(pX, pY);
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)))
                TargetPointBitOn(pX, pY, Bit0, pBatuArr);
        }
        //指定座標の右が未確定の場合
        if (IsBitFalse(pArrowArr[pX, pY] & Bit1) && IsBitFalse(pBatuArr[pX, pY] & Bit1)) {
            //両端のうちの、上方向をチェック
            wkRinsetuBitList = RinsetuCase03_Bit_MigiLine_Ue(pX, pY);
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)))
                TargetPointBitOn(pX, pY, Bit1, pBatuArr);

            //両端のうちの、下方向をチェック
            wkRinsetuBitList = RinsetuCase04_Bit_MigiLine_Shita(pX, pY);
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)))
                TargetPointBitOn(pX, pY, Bit1, pBatuArr);
        }
        //指定座標の下が未確定の場合
        if (IsBitFalse(pArrowArr[pX, pY] & Bit2) && IsBitFalse(pBatuArr[pX, pY] & Bit2)) {
            //両端のうちの、左方向をチェック
            wkRinsetuBitList = RinsetuCase05_Bit_ShitaLine_Hidari(pX, pY);
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)))
                TargetPointBitOn(pX, pY, Bit2, pBatuArr);

            //両端のうちの、右方向をチェック
            wkRinsetuBitList = RinsetuCase06_Bit_ShitaLine_Migi(pX, pY);
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)))
                TargetPointBitOn(pX, pY, Bit2, pBatuArr);
        }
        //指定座標の左が未確定の場合
        if (IsBitFalse(pArrowArr[pX, pY] & Bit3) && IsBitFalse(pBatuArr[pX, pY] & Bit3)) {
            //両端のうちの、上方向をチェック
            wkRinsetuBitList = RinsetuCase07_Bit_HidariLine_Ue(pX, pY);
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)))
                TargetPointBitOn(pX, pY, Bit3, pBatuArr);

            //両端のうちの、下方向をチェック
            wkRinsetuBitList = RinsetuCase08_Bit_HidariLine_Shita(pX, pY);
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)))
                TargetPointBitOn(pX, pY, Bit3, pBatuArr);
        }
    }

    //バツ印を付ける確定探索5 線を引くと、孤立した島ができる場合
    static void BatuKakuteiTansaku5(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        //ビットが3つ確定しているかを判定
        int wkCnt1 = DeriveTargetPointBitCnt(pX, pY, pArrowArr);
        int wkCnt2 = DeriveTargetPointBitCnt(pX, pY, pBatuArr);
        if (wkCnt1 + wkCnt2 != 3) return;

        Action<int> wkAct = (pBit) =>
        {
            if (IsMikakuteiPoint(new PointDef() { X = pX, Y = pY }, pBit, pArrowArr, pBatuArr)) {
                int[,] wkArrowArr = (int[,])pArrowArr.Clone();
                TargetPointBitOn(pX, pY, pBit, wkArrowArr);

                int[,] wkBatuArr = (int[,])pBatuArr.Clone();
                ExecKakuteiTansaku(wkArrowArr, wkBatuArr, false);

                if (IsValid(wkArrowArr)) return;

                TargetPointBitOn(pX, pY, pBit, pBatuArr);
            }
        };

        wkAct(Bit0); wkAct(Bit1); wkAct(Bit2); wkAct(Bit3);
    }

    //バツ印を付ける確定探索6 数値の1の頂点に線とバツが隣接している場合
    static void BatuKakuteiTansaku6(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        int wkInd = Array.FindIndex(NumInfoArr, A => A.Point.X == pX && A.Point.Y == pY);
        if (wkInd == -1) return; //数値マスでない場合
        int CurrNumVal = NumInfoArr[wkInd].NumVal;

        //1でない場合
        if (CurrNumVal != 1) return;

        List<RinsetuBitDef> wkRinsetuBitList = null;

        Action<int, int> KakuteiAct = (pKakuteiBit1, pKakuteiBit2) =>
        {
            //自分の座標はRemove
            wkRinsetuBitList.RemoveAll(A => A.X == pX && A.Y == pY);

            //頂点に線とバツが隣接しているかをチェック
            if (wkRinsetuBitList.Exists(A => IsArrowPoint(new PointDef() { X = A.X, Y = A.Y },
                                                          A.Bit, pArrowArr)) == false) return;
            if (wkRinsetuBitList.Exists(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                         A.Bit, pBatuArr)) == false) return;

            //バツの確定処理
            TargetPointBitOn(pX, pY, pKakuteiBit1, pBatuArr);
            TargetPointBitOn(pX, pY, pKakuteiBit2, pBatuArr);
        };

        //指定座標の上の線の、左端をチェック
        wkRinsetuBitList = RinsetuCase01_Bit_UeLine_Hidari(pX, pY);
        KakuteiAct(Bit1, Bit2);

        //指定座標の上の線の、右端をチェック
        wkRinsetuBitList = RinsetuCase02_Bit_UeLine_Migi(pX, pY);
        KakuteiAct(Bit2, Bit3);

        //指定座標の下の線の、左端をチェック
        wkRinsetuBitList = RinsetuCase05_Bit_ShitaLine_Hidari(pX, pY);
        KakuteiAct(Bit0, Bit1);

        //指定座標の下の線の、右端をチェック
        wkRinsetuBitList = RinsetuCase06_Bit_ShitaLine_Migi(pX, pY);
        KakuteiAct(Bit0, Bit3);
    }

    //数値確定探索1 数値が1か3で頂点にバツ印2つが隣接している場合
    static void SuutiKakuteiTansaku1(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        int wkInd = Array.FindIndex(NumInfoArr, A => A.Point.X == pX && A.Point.Y == pY);
        if (wkInd == -1) return; //数値マスでない場合
        int CurrNumVal = NumInfoArr[wkInd].NumVal;

        //1でも3でもない場合
        if (CurrNumVal != 1 && CurrNumVal != 3) return;

        List<RinsetuBitDef> wkRinsetuBitList = null;

        Action<int, int> KakuteiAct = (pKakuteiBit1, pKakuteiBit2) =>
        {
            //自分の座標はRemove
            wkRinsetuBitList.RemoveAll(A => A.X == pX && A.Y == pY);

            //バツ印2つが隣接しているかをチェック
            if (wkRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                             A.Bit, pBatuArr)) == false)
                return;

            //数値が1の場合は、バツが確定する
            if (CurrNumVal == 1) {
                TargetPointBitOn(pX, pY, pKakuteiBit1, pBatuArr);
                TargetPointBitOn(pX, pY, pKakuteiBit2, pBatuArr);
            }

            //数値が3の場合は、線が確定する
            if (CurrNumVal == 3) {
                TargetPointBitOn(pX, pY, pKakuteiBit1, pArrowArr);
                TargetPointBitOn(pX, pY, pKakuteiBit2, pArrowArr);
            }
        };

        //指定座標の上の線の、左端をチェック
        wkRinsetuBitList = RinsetuCase01_Bit_UeLine_Hidari(pX, pY);
        KakuteiAct(Bit0, Bit3);

        //指定座標の上の線の、右端をチェック
        wkRinsetuBitList = RinsetuCase02_Bit_UeLine_Migi(pX, pY);
        KakuteiAct(Bit0, Bit1);

        //指定座標の下の線の、左端をチェック
        wkRinsetuBitList = RinsetuCase05_Bit_ShitaLine_Hidari(pX, pY);
        KakuteiAct(Bit2, Bit3);

        //指定座標の下の線の、右端をチェック
        wkRinsetuBitList = RinsetuCase06_Bit_ShitaLine_Migi(pX, pY);
        KakuteiAct(Bit1, Bit2);
    }

    //数値確定探索2 数値が2で頂点にバツ印2つが隣接している場合
    static void SuutiKakuteiTansaku2(int pX, int pY, int[,] pArrowArr, int[,] pBatuArr)
    {
        int wkInd = Array.FindIndex(NumInfoArr, A => A.Point.X == pX && A.Point.Y == pY);
        if (wkInd == -1) return; //数値マスでない場合
        int CurrNumVal = NumInfoArr[wkInd].NumVal;

        //2でない場合
        if (CurrNumVal != 2) return;

        //確定ビット数が1でない場合
        int wkCnt1 = DeriveTargetPointBitCnt(pX, pY, pArrowArr);
        int wkCnt2 = DeriveTargetPointBitCnt(pX, pY, pBatuArr);
        if (wkCnt1 + wkCnt2 != 1) return;

        Action<int, int, int[,], List<RinsetuBitDef>> KakuteiAct
            = (pCheckBit, pCascadeBit, pTargetArr, pRinsetuBitList) =>
        {
            if (IsBitFalse(pTargetArr[pX, pY] & pCheckBit)) return;

            //自分の座標はRemove
            pRinsetuBitList.RemoveAll(A => A.X == pX && A.Y == pY);

            //バツ印2つが隣接しているかをチェック
            if (pRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                            A.Bit, pBatuArr)) == false)
                return;

            TargetPointBitOn(pX, pY, pCascadeBit, pTargetArr);
        };

        KakuteiAct(Bit0, Bit1, pArrowArr, RinsetuCase05_Bit_ShitaLine_Hidari(pX, pY));
        KakuteiAct(Bit0, Bit1, pBatuArr, RinsetuCase05_Bit_ShitaLine_Hidari(pX, pY));
        KakuteiAct(Bit0, Bit3, pArrowArr, RinsetuCase06_Bit_ShitaLine_Migi(pX, pY));
        KakuteiAct(Bit0, Bit3, pBatuArr, RinsetuCase06_Bit_ShitaLine_Migi(pX, pY));

        KakuteiAct(Bit1, Bit2, pArrowArr, RinsetuCase07_Bit_HidariLine_Ue(pX, pY));
        KakuteiAct(Bit1, Bit2, pBatuArr, RinsetuCase07_Bit_HidariLine_Ue(pX, pY));
        KakuteiAct(Bit1, Bit0, pArrowArr, RinsetuCase08_Bit_HidariLine_Shita(pX, pY));
        KakuteiAct(Bit1, Bit0, pBatuArr, RinsetuCase08_Bit_HidariLine_Shita(pX, pY));

        KakuteiAct(Bit2, Bit1, pArrowArr, RinsetuCase01_Bit_UeLine_Hidari(pX, pY));
        KakuteiAct(Bit2, Bit1, pBatuArr, RinsetuCase01_Bit_UeLine_Hidari(pX, pY));
        KakuteiAct(Bit2, Bit3, pArrowArr, RinsetuCase02_Bit_UeLine_Migi(pX, pY));
        KakuteiAct(Bit2, Bit3, pBatuArr, RinsetuCase02_Bit_UeLine_Migi(pX, pY));

        KakuteiAct(Bit3, Bit2, pArrowArr, RinsetuCase03_Bit_MigiLine_Ue(pX, pY));
        KakuteiAct(Bit3, Bit2, pBatuArr, RinsetuCase03_Bit_MigiLine_Ue(pX, pY));
        KakuteiAct(Bit3, Bit0, pArrowArr, RinsetuCase04_Bit_MigiLine_Shita(pX, pY));
        KakuteiAct(Bit3, Bit0, pBatuArr, RinsetuCase04_Bit_MigiLine_Shita(pX, pY));
    }

    //4方向のビット値を出力
    static void PrintBitValueArr(int[,] pTargetArr)
    {
        //数値を全角16進数に変換
        Func<int, char> IntToWideHexFunc = (pInt) =>
        {
            if (0 <= pInt && pInt <= 9)
                return (char)('0' + pInt);
            return (char)('A' + pInt - 10);
        };

        var sb = new System.Text.StringBuilder();
        for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
            for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                sb.Append(IntToWideHexFunc(pTargetArr[LoopX, LoopY]));
            }
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }

    struct JyoutaiDef
    {
        internal int[,] ArrowArr;
        internal int[,] BatuArr;
    }

    //第4フェーズ 深さ優先探索
    static void ExecDFS(int[,] pArrowArr, int[,] pBatuArr)
    {
        var stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        WillPush.ArrowArr = pArrowArr;
        WillPush.BatuArr = pBatuArr;
        stk.Push(WillPush);

        if (IsClear(pArrowArr, pBatuArr)) {
            Console.WriteLine("確定探索だけで解を発見");
            PrintAnswer();
            return;
        }

        Console.WriteLine("確定探索だけで解を発見できませんでした。");
        Console.WriteLine("深さ優先を使います。");
        Console.WriteLine();

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

            if (IsClear(Popped.ArrowArr, Popped.BatuArr)) {
                Console.WriteLine("解を発見。経過時間={0}", sw.Elapsed);
                PrintAnswer();
                return;
            }

            Func<int, int, int, bool> IsPushTarget = (pX, pY, pBit) =>
            {
                if (IsBitFalse(Popped.ArrowArr[pX, pY] & pBit)
                 && IsBitFalse(Popped.BatuArr[pX, pY] & pBit)) return true;
                return false;
            };

            //未確定ビットに線もしくはバツ印を設定
            int TargetX = -1, TargetY = -1, TargetBit = -1;
            bool WillBreak = false;

            //数値マスを優先して決定する
            foreach (NumInfoDef EachNumInfo in NumInfoArr) {
                if (IsPushTarget(EachNumInfo.Point.X, EachNumInfo.Point.Y, Bit0)) {
                    TargetX = EachNumInfo.Point.X; TargetY = EachNumInfo.Point.Y; TargetBit = Bit0;
                    WillBreak = true; break;
                }
                if (IsPushTarget(EachNumInfo.Point.X, EachNumInfo.Point.Y, Bit1)) {
                    TargetX = EachNumInfo.Point.X; TargetY = EachNumInfo.Point.Y; TargetBit = Bit1;
                    WillBreak = true; break;
                }
                if (IsPushTarget(EachNumInfo.Point.X, EachNumInfo.Point.Y, Bit2)) {
                    TargetX = EachNumInfo.Point.X; TargetY = EachNumInfo.Point.Y; TargetBit = Bit2;
                    WillBreak = true; break;
                }
                if (IsPushTarget(EachNumInfo.Point.X, EachNumInfo.Point.Y, Bit3)) {
                    TargetX = EachNumInfo.Point.X; TargetY = EachNumInfo.Point.Y; TargetBit = Bit3;
                    WillBreak = true; break;
                }
                if (WillBreak) break;
            }

            //数値マス以外のマス
            for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                if (WillBreak) break;
                for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                    if (Array.Exists(NumInfoArr, A => A.Point.X == LoopX && A.Point.Y == LoopY))
                        continue;

                    if (IsPushTarget(LoopX, LoopY, Bit0)) {
                        TargetX = LoopX; TargetY = LoopY; TargetBit = Bit0;
                        WillBreak = true; break;
                    }
                    if (IsPushTarget(LoopX, LoopY, Bit1)) {
                        TargetX = LoopX; TargetY = LoopY; TargetBit = Bit1;
                        WillBreak = true; break;
                    }
                    if (IsPushTarget(LoopX, LoopY, Bit2)) {
                        TargetX = LoopX; TargetY = LoopY; TargetBit = Bit2;
                        WillBreak = true; break;
                    }
                    if (IsPushTarget(LoopX, LoopY, Bit3)) {
                        TargetX = LoopX; TargetY = LoopY; TargetBit = Bit3;
                        WillBreak = true; break;
                    }
                }
            }

            //Push処理
            if (TargetX != -1) {
                WillPush.ArrowArr = (int[,])Popped.ArrowArr.Clone();
                WillPush.BatuArr = (int[,])Popped.BatuArr.Clone();
                TargetPointBitOn(TargetX, TargetY, TargetBit, WillPush.ArrowArr);
                //Push前に確定探索               
                ExecKakuteiTansaku(WillPush.ArrowArr, WillPush.BatuArr, true);
                if (IsValid(WillPush.ArrowArr)) stk.Push(WillPush);

                WillPush.ArrowArr = (int[,])Popped.ArrowArr.Clone();
                WillPush.BatuArr = (int[,])Popped.BatuArr.Clone();
                TargetPointBitOn(TargetX, TargetY, TargetBit, WillPush.BatuArr);
                //Push前に確定探索               
                ExecKakuteiTansaku(WillPush.ArrowArr, WillPush.BatuArr, true);
                if (IsValid(WillPush.ArrowArr)) stk.Push(WillPush);
            }
        }
    }

    //盤面が有効な状態かを判定
    static bool IsValid(int[,] pArrowArr)
    {
        //内側の島の座標のList
        var UtigawaShimaList = new List<PointDef>();

        //外側の島の座標のList
        var SotogawaShimaList = new List<PointDef>();

        //外周の時計回りで、UnionFindの開始座標とする
        bool PushedUtigawa = false;
        for (int LoopX = 0; LoopX <= UB_X - 1; LoopX++) {
            if (IsBitTrue(pArrowArr[LoopX, 0] & Bit0)) { //内側の島かを判定
                if (PushedUtigawa) { //内側の島をPush済の場合
                    if (UtigawaShimaList.Exists( //別の内側の島から到達不能ならNG
                        A => A.X == LoopX && A.Y == 0) == false) {
                        return false;
                    }
                    continue;
                }
                PushedUtigawa = true;
                ExecUnionFind(LoopX, 0, pArrowArr, UtigawaShimaList);
            }
            else ExecUnionFind(LoopX, 0, pArrowArr, SotogawaShimaList);
        }
        for (int LoopY = 0; LoopY <= UB_Y - 1; LoopY++) {
            if (IsBitTrue(pArrowArr[UB_X, LoopY] & Bit1)) { //内側の島かを判定
                if (PushedUtigawa) { //内側の島をPush済の場合
                    if (UtigawaShimaList.Exists( //別の内側の島から到達不能ならNG
                        A => A.X == UB_X && A.Y == LoopY) == false) {
                        return false;
                    }
                    continue;
                }
                PushedUtigawa = true;
                ExecUnionFind(UB_X, LoopY, pArrowArr, UtigawaShimaList);
            }
            else ExecUnionFind(UB_X, LoopY, pArrowArr, SotogawaShimaList);
        }
        for (int LoopX = UB_X; 1 <= LoopX; LoopX--) {
            if (IsBitTrue(pArrowArr[LoopX, UB_Y] & Bit2)) { //内側の島かを判定
                if (PushedUtigawa) { //内側の島をPush済の場合
                    if (UtigawaShimaList.Exists( //別の内側の島から到達不能ならNG
                        A => A.X == LoopX && A.Y == UB_Y) == false) {
                        return false;
                    }
                    continue;
                }
                PushedUtigawa = true;
                ExecUnionFind(LoopX, UB_Y, pArrowArr, UtigawaShimaList);
            }
            else ExecUnionFind(LoopX, UB_Y, pArrowArr, SotogawaShimaList);
        }
        for (int LoopY = UB_Y; 1 <= LoopY; LoopY--) {
            if (IsBitTrue(pArrowArr[0, LoopY] & Bit3)) { //内側の島かを判定
                if (PushedUtigawa) { //内側の島をPush済の場合
                    if (UtigawaShimaList.Exists( //別の内側の島から到達不能ならNG
                        A => A.X == 0 && A.Y == LoopY) == false) {
                        return false;
                    }
                    continue;
                }
                PushedUtigawa = true;
                ExecUnionFind(0, LoopY, pArrowArr, UtigawaShimaList);
            }
            else ExecUnionFind(0, LoopY, pArrowArr, SotogawaShimaList);
        }

        //内側の島と外側の島の座標をDistinctする
        var ConcatedList = UtigawaShimaList.Concat(SotogawaShimaList).ToList();
        for (int I = ConcatedList.Count - 1; 0 <= I; I--) {
            for (int J = 0; J <= I - 1; J++) {
                if (ConcatedList[I].X == ConcatedList[J].X
                 && ConcatedList[I].Y == ConcatedList[J].Y) {
                    ConcatedList.RemoveAt(I);
                }
            }
        }

        //島の数 = 盤面かを判定
        return ConcatedList.Count == (UB_X + 1) * (UB_Y + 1);
    }

    //クリア判定
    static bool IsClear(int[,] pArrowArr, int[,] pBatuArr)
    {
        if (ClearHantei1(pArrowArr, pBatuArr) == false) return false;
        if (ClearHantei2(pArrowArr) == false) return false;
        if (ClearHantei3(pArrowArr, pBatuArr) == false) return false;
        if (ClearHantei4(pArrowArr) == false) return false;
        return true;
    }

    //クリア条件1 未確定ビットがないこと
    static bool ClearHantei1(int[,] pArrowArr, int[,] pBatuArr)
    {
        for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
            for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {

                Predicate<int> wkPred = pBit =>
                {
                    if (IsBitFalse(pBatuArr[LoopX, LoopY] & pBit)
                     && IsBitFalse(pArrowArr[LoopX, LoopY] & pBit)) return false;
                    return true;
                };

                if (wkPred(Bit0) == false) return false;
                if (wkPred(Bit1) == false) return false;
                if (wkPred(Bit2) == false) return false;
                if (wkPred(Bit3) == false) return false;
            }
        }
        return true;
    }

    //クリア条件2 全ての数値マスが線の数を満たしていること
    static bool ClearHantei2(int[,] pArrowArr)
    {
        foreach (NumInfoDef EachNumInfo in NumInfoArr) {
            int X = EachNumInfo.Point.X;
            int Y = EachNumInfo.Point.Y;
            int ArrowCnt = DeriveTargetPointBitCnt(X, Y, pArrowArr);

            if (ArrowCnt != EachNumInfo.NumVal) return false;
        }
        return true;
    }

    //クリア条件3 切れた線がないこと
    static bool ClearHantei3(int[,] pArrowArr, int[,] pBatuArr)
    {
        for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
            for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {

                //線が切れてるかを判定
                Predicate<List<RinsetuBitDef>> wkPred = pRinsetuBitList =>
                    pRinsetuBitList.TrueForAll(A => IsBatuPoint(new PointDef() { X = A.X, Y = A.Y },
                                                                A.Bit, pBatuArr));

                //上の線が、切れてる場合
                if (IsBitTrue(pArrowArr[LoopX, LoopY] & Bit0)) {
                    if (wkPred(RinsetuCase01_Bit_UeLine_Hidari(LoopX, LoopY))) return false;
                    if (wkPred(RinsetuCase02_Bit_UeLine_Migi(LoopX, LoopY))) return false;
                }
                //右の線が、切れてる場合
                if (IsBitTrue(pArrowArr[LoopX, LoopY] & Bit1)) {
                    if (wkPred(RinsetuCase03_Bit_MigiLine_Ue(LoopX, LoopY))) return false;
                    if (wkPred(RinsetuCase04_Bit_MigiLine_Shita(LoopX, LoopY))) return false;
                }
                //下の線が、切れてる場合
                if (IsBitTrue(pArrowArr[LoopX, LoopY] & Bit2)) {
                    if (wkPred(RinsetuCase05_Bit_ShitaLine_Hidari(LoopX, LoopY))) return false;
                    if (wkPred(RinsetuCase06_Bit_ShitaLine_Migi(LoopX, LoopY))) return false;
                }
                //左の線が、切れてる場合
                if (IsBitTrue(pArrowArr[LoopX, LoopY] & Bit3)) {
                    if (wkPred(RinsetuCase07_Bit_HidariLine_Ue(LoopX, LoopY))) return false;
                    if (wkPred(RinsetuCase08_Bit_HidariLine_Shita(LoopX, LoopY))) return false;
                }
            }
        }
        return true;
    }

    //クリア条件4 内側になる島が1つしかないこと
    static bool ClearHantei4(int[,] pArrowArr)
    {
        //内側の島の座標のList
        AnswerUtigawaShimaList.Clear();

        //外側の島の座標のList
        var SotogawaShimaList = new List<PointDef>();

        //外周の時計回りで、内側か外側かを判定しつつ、UnionFindの開始座標とする
        bool PushedUtigawa = false;
        for (int LoopX = 0; LoopX <= UB_X - 1; LoopX++) {
            if (IsBitTrue(pArrowArr[LoopX, 0] & Bit0)) { //内側の島かを判定
                if (PushedUtigawa) continue;
                ExecUnionFind(LoopX, 0, pArrowArr, AnswerUtigawaShimaList);
                PushedUtigawa = true;
            }
            else ExecUnionFind(LoopX, 0, pArrowArr, SotogawaShimaList);
        }
        for (int LoopY = 0; LoopY <= UB_Y - 1; LoopY++) {
            if (IsBitTrue(pArrowArr[UB_X, LoopY] & Bit1)) { //内側の島かを判定
                if (PushedUtigawa) continue;
                ExecUnionFind(UB_X, LoopY, pArrowArr, AnswerUtigawaShimaList);
                PushedUtigawa = true;
            }
            else ExecUnionFind(UB_X, LoopY, pArrowArr, SotogawaShimaList);
        }
        for (int LoopX = UB_X; 1 <= LoopX; LoopX--) {
            if (IsBitTrue(pArrowArr[LoopX, UB_Y] & Bit2)) { //内側の島かを判定
                if (PushedUtigawa) continue;
                ExecUnionFind(LoopX, UB_Y, pArrowArr, AnswerUtigawaShimaList);
                PushedUtigawa = true;
            }
            else ExecUnionFind(LoopX, UB_Y, pArrowArr, SotogawaShimaList);
        }
        for (int LoopY = UB_Y; 1 <= LoopY; LoopY--) {
            if (IsBitTrue(pArrowArr[0, LoopY] & Bit3)) { //内側の島かを判定
                if (PushedUtigawa) continue;
                ExecUnionFind(0, LoopY, pArrowArr, AnswerUtigawaShimaList);
                PushedUtigawa = true;
            }
            else ExecUnionFind(0, LoopY, pArrowArr, SotogawaShimaList);
        }

        //内側の島の数 + 外側の島の数 = 盤面
        int ShimaCnt = AnswerUtigawaShimaList.Count + SotogawaShimaList.Count;
        return ShimaCnt == (UB_X + 1) * (UB_Y + 1);
    }

    //島の座標のListを引数として、UnionFindで、線を越えずに移動可能な座標をAddする
    static void ExecUnionFind(int pBaseX, int pBaseY, int[,] pArrowArr, List<PointDef> pShimaList)
    {
        var stk = new Stack<PointDef>();
        PointDef WillPush;

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

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

        PushSyori(pBaseX, pBaseY);
        while (stk.Count > 0) {
            PointDef Popped = stk.Pop();

            //左に移動
            if (Popped.X > 0 && IsBitFalse(pArrowArr[Popped.X, Popped.Y] & Bit3))
                PushSyori(Popped.X - 1, Popped.Y);
            //右に移動
            if (Popped.X < UB_X && IsBitFalse(pArrowArr[Popped.X, Popped.Y] & Bit1))
                PushSyori(Popped.X + 1, Popped.Y);
            //上に移動
            if (Popped.Y > 0 && IsBitFalse(pArrowArr[Popped.X, Popped.Y] & Bit0))
                PushSyori(Popped.X, Popped.Y - 1);
            //下に移動
            if (Popped.Y < UB_Y && IsBitFalse(pArrowArr[Popped.X, Popped.Y] & Bit2))
                PushSyori(Popped.X, Popped.Y + 1);
        }
    }

    //解を出力
    static void PrintAnswer()
    {
        //半角数字を全角数字に変換
        Func<int, char> SingleToWideFunc = (pStr) => (char)('0' + pStr);

        var sb = new System.Text.StringBuilder();
        for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
            for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                //内側の島の場合
                int wkInd1 = AnswerUtigawaShimaList.FindIndex(A => A.X == LoopX && A.Y == LoopY);
                if (wkInd1 >= 0) {
                    sb.Append('■');
                    continue;
                }

                //数値マスの場合
                int wkInd2 = Array.FindIndex(NumInfoArr, A => A.Point.X == LoopX && A.Point.Y == LoopY);
                if (wkInd2 >= 0) {
                    sb.Append(SingleToWideFunc(NumInfoArr[wkInd2].NumVal));
                    continue;
                }
                sb.Append('□');
            }
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }
}


実行結果

確定探索だけで解を発見
□0□1■■■■
■3■□■3■2
■■■■■□□0
□3■■■■■□
■■■3■■■■
■■□□■3■■
■□1■■□3■
□0□□■□■■


追加問題02 ニコリビヨリのサンプル問題

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


追加問題03 ニコリビヨリの問題

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


追加問題04 ペンシルパズル入門の例題

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


追加問題05 ペンシルパズル入門の1問目

char[,] Q05Arr = {{'2',' ',' ','2','0'},
                  {' ','3','2',' ',' '},
                  {'2',' ',' ',' ','2'},
                  {' ',' ','0','3',' '},
                  {'2','3',' ',' ','2'}};


追加問題06 ペンシルパズル入門の2問目

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


追加問題07 ペンシルパズル入門の3問目

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


追加問題08 ペンシルパズル入門の4問目

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


追加問題09 ペンシルパズル入門の5問目

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


追加問題10 ペンシルパズル入門の6問目

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


追加問題11 ペンシルパズル入門の7問目

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


追加問題12 ペンシルパズル入門の8問目

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


追加問題13 パズルBox12の1問目

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


追加問題14 パズルBox12の5問目

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


追加問題15 パズルBox10の07問目

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


検証用のWindowsフォームアプリのソースと実行結果

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace SlitherLinkGUI
{
    public partial class Form1 : Form
    {
        static List<int[]> HaitiArrowIntArrList;
        static List<int[]> HaitiBatuIntArrList;

        const int MasuHaba = 35;
        //const int MasuHaba = 50;
        static int UB_X;
        static int UB_Y;

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

        static char[,] QuestionArr = Q01Arr;

        //X座標とY座標の入れ替え
        static char[,] XYRev(char[,] pBaseArr)
        {
            int RevArrUB_X = pBaseArr.GetUpperBound(1);
            int RevArrUB_Y = pBaseArr.GetUpperBound(0);
            char[,] WillReturnArr = new char[RevArrUB_X + 1, RevArrUB_Y + 1];
            for (int X = 0; X <= RevArrUB_X; X++) {
                for (int Y = 0; Y <= RevArrUB_Y; Y++) {
                    WillReturnArr[X, Y] = pBaseArr[Y, X];
                }
            }
            return WillReturnArr;
        }

        public Form1()
        {
            QuestionArr = XYRev(QuestionArr);
            UB_X = QuestionArr.GetUpperBound(0);
            UB_Y = QuestionArr.GetUpperBound(1);

            var wkArrowHaitiList = new List<string>();
            wkArrowHaitiList.Add("BC00000000000000069153C04");
            wkArrowHaitiList.Add("C3828000000000002D02BC7AB");
            wkArrowHaitiList.Add("7A8280040040000003800156A");
            wkArrowHaitiList.Add("96840003C2906800000002D12");
            wkArrowHaitiList.Add("A92D0000102AD0000000283C2");
            wkArrowHaitiList.Add("AC07A8000000380000002C07A");
            wkArrowHaitiList.Add("87A900000000000402C047AD2");
            wkArrowHaitiList.Add("A928000000040007807AD503A");
            wkArrowHaitiList.Add("A82AC0000029000102D05382A");
            wkArrowHaitiList.Add("A8283C000002E800043ABE82A");
            wkArrowHaitiList.Add("AC2C47802A8050006BEAC142A");
            wkArrowHaitiList.Add("C3C511046AC2B82C54507ABEA");
            wkArrowHaitiList.Add("3C13C02D107AA801113E92852");
            wkArrowHaitiList.Add("07803C4782D6AC44444546ABE");
            wkArrowHaitiList.Add("2D44455544554555555555681");

            var wkBatuHaitiList = new List<string>();
            wkBatuHaitiList.Add("43FD000000000000796EAC3FB");
            wkBatuHaitiList.Add("3C7D4006C040000052F943854");
            wkBatuHaitiList.Add("857D1003FEB80002F8103EA95");
            wkBatuHaitiList.Add("69790004390684001000052ED");
            wkBatuHaitiList.Add("56D2C00380052F80000047C3D");
            wkBatuHaitiList.Add("53F85000000381004006D3F85");
            wkBatuHaitiList.Add("7856900000040002FC2FB852D");
            wkBatuHaitiList.Add("56D50400406B84287F852AFC5");
            wkBatuHaitiList.Add("57D52FC2F8104382FD2FAC7D5");
            wkBatuHaitiList.Add("57D7C3FC140414007BC5417D5");
            wkBatuHaitiList.Add("53D3B87FC56FAFC014153EBD5");
            wkBatuHaitiList.Add("3C3AEEFB953D47D2ABAF85415");
            wkBatuHaitiList.Add("C3EC3FD2EF8557FEEEC16D7AD");
            wkBatuHaitiList.Add("F87FC3B87D2953BBBBBAB9541");
            wkBatuHaitiList.Add("D2BBBAAABBAABAAAAAAAAA97E");

            HaitiArrowIntArrList = DeriveConvertedIntArrList(wkArrowHaitiList);
            HaitiBatuIntArrList = DeriveConvertedIntArrList(wkBatuHaitiList);

            InitializeComponent();
        }

        //大文字の16進数の文字列をInt型の配列に変換
        static List<int[]> DeriveConvertedIntArrList(List<string> wkStringList)
        {
            //全角16進数を数値に変換
            Func<char, int> WideHexToIntFunc = (pChar) =>
            {
                if ('0' <= pChar && pChar <= '9')
                    return pChar - '0';
                return pChar - 'A' + 10;
            };

            var WillReturnList = new List<int[]>();

            foreach (string EachStr in wkStringList) {
                int[] wkIntArr = new int[EachStr.Length];
                for (int I = 0; I <= wkIntArr.GetUpperBound(0); I++) {
                    wkIntArr[I] = WideHexToIntFunc(EachStr[I]);
                }
                WillReturnList.Add(wkIntArr);
            }
            return WillReturnList;
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            Bitmap Canvas = new Bitmap(PictureBox1.Width, PictureBox1.Height);
            using (Graphics g = Graphics.FromImage(Canvas)) {
                for (int X = 0; X <= UB_X; X++) {
                    for (int Y = 0; Y <= UB_Y; Y++) {
                        int BaseX = MasuHaba * X;
                        int BaseY = MasuHaba * Y;

                        g.DrawLine(Pens.Black, BaseX, BaseY,
                                               BaseX + MasuHaba, BaseY);
                        g.DrawLine(Pens.Black, BaseX, BaseY,
                                               BaseX, BaseY + MasuHaba);
                        g.DrawLine(Pens.Black, BaseX + MasuHaba, BaseY,
                                               BaseX + MasuHaba, BaseY + MasuHaba);
                        g.DrawLine(Pens.Black, BaseX, BaseY + MasuHaba,
                                               BaseX + MasuHaba, BaseY + MasuHaba);
                    }
                }
                //数値を描画
                PaintNum(g);

                //線を描画
                PaintArror(g);

                //バツを描画
                PaintBatu(g);
            }
            PictureBox1.Image = Canvas;
        }

        //数値を描画
        static void PaintNum(Graphics pGraphics)
        {
            //半角数字を全角数字に変換
            Func<char, char> SingleToWideFunc = (pStr) => (char)('0' + pStr - '0');

            for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                    if (QuestionArr[LoopX, LoopY] == ' ')
                        continue;

                    int BaseX = MasuHaba * LoopX;
                    int BaseY = MasuHaba * LoopY;

                    Font fnt = new Font("MS ゴシック", 12);
                    Point wkPoint = new Point() { X = BaseX + MasuHaba / 3, Y = BaseY + MasuHaba / 3 };

                    char wkChar = SingleToWideFunc(QuestionArr[LoopX, LoopY]);
                    pGraphics.DrawString(wkChar.ToString(), fnt, Brushes.Black, wkPoint);
                }
            }
        }

        //線を描画
        static void PaintArror(Graphics pGraphics)
        {
            for (int Y = 0; Y <= HaitiArrowIntArrList.Count - 1; Y++) {
                int[] CurrIntArr = HaitiArrowIntArrList[Y];
                for (int X = 0; X <= CurrIntArr.GetUpperBound(0); X++) {
                    int CurrInt = CurrIntArr[X];

                    int BaseX = MasuHaba * X;
                    int BaseY = MasuHaba * Y;
                    var wkPen = new Pen(Color.Blue, 5);

                    if (IsBitTrue(CurrInt & Bit0)) {
                        pGraphics.DrawLine(wkPen, BaseX, BaseY,
                                                  BaseX + MasuHaba, BaseY);
                    }

                    if (IsBitTrue(CurrInt & Bit1)) {
                        pGraphics.DrawLine(wkPen, BaseX + MasuHaba, BaseY,
                                                  BaseX + MasuHaba, BaseY + MasuHaba);
                    }

                    if (IsBitTrue(CurrInt & Bit2)) {
                        pGraphics.DrawLine(wkPen, BaseX, BaseY + MasuHaba,
                                                  BaseX + MasuHaba, BaseY + MasuHaba);
                    }

                    if (IsBitTrue(CurrInt & Bit3)) {
                        pGraphics.DrawLine(wkPen, BaseX, BaseY,
                                                  BaseX, BaseY + MasuHaba);
                    }
                    wkPen.Dispose();
                }
            }
        }

        //4方向のビット定義
        const int Bit0 = 1; //上方向
        const int Bit1 = 2; //右方向
        const int Bit2 = 4; //下方向
        const int Bit3 = 8; //左方向

        //ビット演算の結果が0より大きいかを返す
        static bool IsBitTrue(int pInt)
        {
            return pInt > 0;
        }

        //バツを描画
        static void PaintBatu(Graphics pGraphics)
        {
            for (int Y = 0; Y <= HaitiBatuIntArrList.Count - 1; Y++) {
                int[] CurrIntArr = HaitiBatuIntArrList[Y];
                for (int X = 0; X <= CurrIntArr.GetUpperBound(0); X++) {
                    int CurrInt = CurrIntArr[X];

                    int BaseX = MasuHaba * X;
                    int BaseY = MasuHaba * Y;
                    var wkPen = new Pen(Color.Red, 5);

                    if (IsBitTrue(CurrInt & Bit0)) {
                        pGraphics.DrawLine(wkPen, BaseX + MasuHaba / 3, BaseY,
                                                  BaseX + MasuHaba / 3 * 2, BaseY);
                    }

                    if (IsBitTrue(CurrInt & Bit1)) {
                        pGraphics.DrawLine(wkPen, BaseX + MasuHaba, BaseY + MasuHaba / 3,
                                                  BaseX + MasuHaba, BaseY + MasuHaba / 3 * 2);
                    }

                    if (IsBitTrue(CurrInt & Bit2)) {
                        pGraphics.DrawLine(wkPen, BaseX + MasuHaba / 3, BaseY + MasuHaba,
                                                  BaseX + MasuHaba / 3 * 2, BaseY + MasuHaba);
                    }

                    if (IsBitTrue(CurrInt & Bit3)) {
                        pGraphics.DrawLine(wkPen, BaseX, BaseY + MasuHaba / 3,
                                                  BaseX, BaseY + MasuHaba / 3 * 2);
                    }
                    wkPen.Dispose();
                }
            }
        }
    }
}

実行結果


解説

16進数で、座標の周りの線もしくはバツ印の管理をしてます。