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

24-25 ナンバーリンク

問題

ニコリのナンバーリンクを解きます。

例題と答え
    

1 白マスに線を引いて、同じ数字どうしをつなげましょう。

2 線は、マスの中央を通るようにタテヨコに引きます。
  線を交差させたり、枝分かれさせたりしてはいけません。

3 数字の入っているマスを通過するように線を引いてはいけません。

4 1マスに2本以上の線を引いてはいけません。


ソース(作成中その1)

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

class Program
{
    static int mQuestionNo = 2;
    static char[,] mQuestionArr; //問題の初期盤面

    static int UB;

    //問題を定義
    static void QuestionDef()
    {
        string[] wkStrArr = null;
        if (mQuestionNo == 1) {
            wkStrArr = new string[] {"12□□5□",
                                     "□□□□4□",
                                     "□3□□□□",
                                     "□□□2□□",
                                     "□14□□5",
                                     "□□□□□3"};
        }
        if (mQuestionNo == 2) {
            wkStrArr = new string[] {"5□4□□□□□□5",
                                     "1□□□□□□□□□",
                                     "□□□□□□1□□□",
                                     "□□□□□□□□□□",
                                     "□□□□□□□□□□",
                                     "□□□□□□□□2□",
                                     "□□□3□□□□□□",
                                     "□□□□□□□2□□",
                                     "□□□□□□□43□",
                                     "□□□□□□□□□□"};
        }
        if (mQuestionNo == 3) {
            wkStrArr = new string[] {"□□16□□4□□□",
                                     "□□□□7□□□□□",
                                     "□□□□□□□5□□",
                                     "□□□□2□□□□□",
                                     "□□□□3□4□□□",
                                     "□□□□□□□□□6",
                                     "□□□□□□□□□□",
                                     "□□□□□□□□□□",
                                     "□75□□□□12□",
                                     "□□□□3□□□□□"};
        }
        UB = wkStrArr.GetUpperBound(0);

        mQuestionArr = new char[UB + 1, UB + 1];
        for (int Y = 0; Y <= UB; Y++)
            for (int X = 0; X <= UB; X++)
                mQuestionArr[Y, X] = wkStrArr[X][Y];
    }

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

        //問題を定義
        QuestionDef();

        //リンクの開始座標と終了座標を取得
        DeriveLinkInfoArr();

        var PrevBanArrList = new List<char[,]>() { mQuestionArr };
        foreach (LinkInfoDef EachLinkInfo in mLinkInfoArr) {
            var CurrBanArrList = new List<char[,]>();
            PrevBanArrList.ForEach(X => CurrBanArrList.AddRange(ExecDFS(X, EachLinkInfo)));
            PrevBanArrList = CurrBanArrList;
            Console.WriteLine("{0}のリンクは{1}通り", EachLinkInfo.ID, CurrBanArrList.Count);

            CurrBanArrList.ForEach(A => PrintBan(A));
        }
        for (int I = 0; I <= PrevBanArrList.Count - 1; I++) {
            Console.WriteLine("解{0}", I + 1);
            PrintBan(PrevBanArrList[I]);
        }
        Console.WriteLine("経過時間={0}", sw.Elapsed);
    }

    struct JyoutaiDefDFS
    {
        internal int CurrX;
        internal int CurrY;
        internal char[,] BanArr;
    }

    //深さ優先探索でリンクを作成
    static List<char[,]> ExecDFS(char[,] pBanArr, LinkInfoDef pLinkInfo)
    {
        var WillReturn = new List<char[,]>();

        var Stk = new Stack<JyoutaiDefDFS>();
        JyoutaiDefDFS WillPush;
        WillPush.CurrX = pLinkInfo.StaX;
        WillPush.CurrY = pLinkInfo.StaY;
        WillPush.BanArr = pBanArr;
        Stk.Push(WillPush);

        int PopCnt = 0;

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

            if (++PopCnt % 10000 == 0) {
                Console.WriteLine("{0}回目のPop", PopCnt);
                PrintBan(Popped.BanArr);
            }

            //クリア判定(終点の4近傍にいたら探索終了)
            bool IsOK = false;
            if (Popped.CurrX == pLinkInfo.EndX && Popped.CurrY == pLinkInfo.EndY - 1) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX && Popped.CurrY == pLinkInfo.EndY + 1) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX - 1 && Popped.CurrY == pLinkInfo.EndY) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX + 1 && Popped.CurrY == pLinkInfo.EndY) IsOK = true;
            if (IsOK) {
                WillReturn.Add(Popped.BanArr);
                continue;
            }

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

                if (Popped.BanArr[pNewX, pNewY] != '□') return;

                //ゴールでも直近でもないマスと隣接していたら枝切り
                PointDef PrevPos = new PointDef() { X = Popped.CurrX, Y = Popped.CurrY };
                PointDef CurrPos = new PointDef() { X = pNewX, Y = pNewY };
                PointDef GoalPos = new PointDef() { X = pLinkInfo.EndX, Y = pLinkInfo.EndY };
                List<char> wk4KinbouList = Derive4KinbouList(Popped.BanArr, PrevPos, CurrPos, GoalPos);
                if (wk4KinbouList.Contains(pLinkInfo.ID)) return;

                WillPush.CurrX = pNewX;
                WillPush.CurrY = pNewY;
                WillPush.BanArr = (char[,])Popped.BanArr.Clone();
                WillPush.BanArr[pNewX, pNewY] = pLinkInfo.ID;

                //リンクが凹の形なら枝切り
                if (HasHekomiLink(WillPush.BanArr)) return;

                //UnionFindで枝切り判定
                if (WillEdakiriUnionFind(WillPush.BanArr, pLinkInfo.ID)) return;

                //空白の島に同じ数字が2箇所隣接しているかを判定
                if (IsValidShima(WillPush.BanArr, pLinkInfo.ID) == false) return;

                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);
        }
        return WillReturn;
    }

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

    //4近傍の値のList(直近とゴールは除外)を返す
    static List<char> Derive4KinbouList(char[,] pBanArr,
        PointDef pPrevPoint, PointDef pCurrPoint, PointDef pGoalPoint)
    {
        var WillReturn = new List<char>();

        int CurrX = pCurrPoint.X;
        int CurrY = pCurrPoint.Y;

        Action<int, int> wkAct = (pTargetX, pTargetY) =>
        {
            if (pTargetX < 0 || UB < pTargetX) return;
            if (pTargetY < 0 || UB < pTargetY) return;

            if (pTargetX == pPrevPoint.X && pTargetY == pPrevPoint.Y)
                return;
            if (pTargetX == pGoalPoint.X && pTargetY == pGoalPoint.Y)
                return;

            WillReturn.Add(pBanArr[pTargetX, pTargetY]);
        };

        wkAct(CurrX, CurrY - 1);
        wkAct(CurrX, CurrY + 1);
        wkAct(CurrX - 1, CurrY);
        wkAct(CurrX + 1, CurrY);

        return WillReturn;
    }

    //UnionFindで枝切り判定
    static bool WillEdakiriUnionFind(char[,] pBanArr, char pID)
    {
        foreach (LinkInfoDef EachLinkInfo in mLinkInfoArr) {
            if (pID.CompareTo(EachLinkInfo.ID) >= 0) continue;
            if (ExecUnionFind(pBanArr, EachLinkInfo) == false)
                return true;
        }
        return false;
    }

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

    //UnionFindでリンクを作成可能かを返す
    static bool ExecUnionFind(char[,] pBanArr, LinkInfoDef pLinkInfo)
    {
        var Stk = new Stack<JyoutaiDefUnionFind>();
        JyoutaiDefUnionFind WillPush;
        WillPush.CurrX = pLinkInfo.StaX;
        WillPush.CurrY = pLinkInfo.StaY;
        Stk.Push(WillPush);

        var VisitedSet = new HashSet<int>();
        VisitedSet.Add(PointToInt(WillPush.CurrX, WillPush.CurrY));
        while (Stk.Count > 0) {
            JyoutaiDefUnionFind Popped = Stk.Pop();

            //クリア判定(終点の4近傍にいたら探索終了)
            bool IsOK = false;
            if (Popped.CurrX == pLinkInfo.EndX && Popped.CurrY == pLinkInfo.EndY - 1) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX && Popped.CurrY == pLinkInfo.EndY + 1) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX - 1 && Popped.CurrY == pLinkInfo.EndY) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX + 1 && Popped.CurrY == pLinkInfo.EndY) IsOK = true;
            if (IsOK) return true;

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

                if (pBanArr[pNewX, pNewY] != '□') return;

                //再訪防止
                if (VisitedSet.Add(PointToInt(pNewX, pNewY))) {
                    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);
        }
        return false;
    }

    //空白の島に同じ数字が2箇所隣接しているかを判定
    static bool IsValidShima(char[,] pBanArr, char pID)
    {
        //チェック済の島のSet
        var CheckedPosSet = new HashSet<int>();

        for (int X = 0; X <= UB; X++) {
            for (int Y = 0; Y <= UB; Y++) {
                if (pBanArr[X, Y] != '□') continue;

                if (CheckedPosSet.Add(PointToInt(X, Y))) {
                    if (IsValidShimaHelper(pBanArr, CheckedPosSet, X, Y, pID) == false) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    //空白の島に同じ数字に2箇所隣接しているかを判定
    static bool IsValidShimaHelper(char[,] pBanArr, HashSet<int> pCheckedPosSet,
        int pStaX, int pStaY, char pID)
    {
        var Stk = new Stack<JyoutaiDefUnionFind>();
        JyoutaiDefUnionFind WillPush;
        WillPush.CurrX = pStaX;
        WillPush.CurrY = pStaY;
        Stk.Push(WillPush);

        var RinsetuNumList = new List<char>();
        var VisitedSet = new HashSet<int>();
        VisitedSet.Add(PointToInt(pStaX, pStaY));

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

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

                //再訪防止
                if (VisitedSet.Add(PointToInt(pNewX, pNewY))) {
                    if (pBanArr[pNewX, pNewY] != '□') {
                        RinsetuNumList.Add(pBanArr[pNewX, pNewY]);
                        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);
        }

        //2つだけ隣接した数字がなかったらNG
        if (RinsetuNumList.GroupBy(A => A).Any(A => A.Count() == 2 && A.Key >= pID) == false) {
            return false;
        }
        pCheckedPosSet.UnionWith(VisitedSet);
        return true;
    }

    //リンクが凹の形なら枝切り
    static bool HasHekomiLink(char[,] pBanArr)
    {
        for (int X = 0; X <= UB; X++) {
            for (int Y = 0; Y <= UB; Y++) {
                if (pBanArr[X, Y] != '□') continue;

                var NumList = new List<char>();

                Action<int, int> wkAct = (pTargetX, pTargetY) =>
                {
                    if (pTargetX < 0 || UB < pTargetX) return;
                    if (pTargetY < 0 || UB < pTargetY) return;

                    if (pBanArr[pTargetX, pTargetY] == '□') return;

                    NumList.Add(pBanArr[pTargetX, pTargetY]);
                };

                wkAct(X, Y - 1);
                wkAct(X, Y+ 1);
                wkAct(X- 1, Y);
                wkAct(X+ 1, Y);

                if (NumList.GroupBy(A => A).Any(A => A.Count() >= 3))
                    return true;
            }
        }
        return false;
    }

    struct LinkInfoDef
    {
        internal char ID;
        internal int StaX;
        internal int StaY;
        internal int EndX;
        internal int EndY;
    }
    static LinkInfoDef[] mLinkInfoArr;

    //リンクの開始座標と終了座標を取得
    static void DeriveLinkInfoArr()
    {
        var LinkInfoList = new List<LinkInfoDef>();
        char[] IDArr = mQuestionArr.Cast<char>().Where(A => A != '□').Distinct().ToArray();
        Array.Sort(IDArr);

        LinkInfoDef WillAdd;
        foreach (char EachID in IDArr) {
            WillAdd.ID = EachID;
            WillAdd.StaX = WillAdd.StaY = -1;
            WillAdd.EndX = WillAdd.EndY = -1;

            int HitCnt = 0;
            for (int Y = 0; Y <= UB; Y++) {
                for (int X = 0; X <= UB; X++) {
                    if (mQuestionArr[X, Y] != EachID) continue;
                    HitCnt++;
                    if (HitCnt == 1) {
                        WillAdd.StaX = X; WillAdd.StaY = Y;
                    }
                    if (HitCnt == 2) {
                        WillAdd.EndX = X; WillAdd.EndY = Y;
                        LinkInfoList.Add(WillAdd);
                    }
                }
            }
        }
        mLinkInfoArr = LinkInfoList.ToArray();
    }

    //X座標とY座標のペアをInt型に変換
    static int PointToInt(int pX, int pY)
    {
        return pX * (UB + 1) + pY;
    }

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


ソース(作成中その2)

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

class Program
{
    static int mQuestionNo = 5;
    static char[,] mQuestionArr; //問題の初期盤面

    static int UB;

    //問題を定義
    static void QuestionDef()
    {
        string[] wkStrArr = null;
        if (mQuestionNo == 1) {
            wkStrArr = new string[] {"12□□5□",
                                     "□□□□4□",
                                     "□3□□□□",
                                     "□□□2□□",
                                     "□14□□5",
                                     "□□□□□3"};
        }
        if (mQuestionNo == 2) {
            wkStrArr = new string[] {"5□4□□□□□□5",
                                     "1□□□□□□□□□",
                                     "□□□□□□1□□□",
                                     "□□□□□□□□□□",
                                     "□□□□□□□□□□",
                                     "□□□□□□□□2□",
                                     "□□□3□□□□□□",
                                     "□□□□□□□2□□",
                                     "□□□□□□□43□",
                                     "□□□□□□□□□□"};
        }
        if (mQuestionNo == 3) {
            wkStrArr = new string[] {"□□16□□4□□□",
                                     "□□□□7□□□□□",
                                     "□□□□□□□5□□",
                                     "□□□□2□□□□□",
                                     "□□□□3□4□□□",
                                     "□□□□□□□□□6",
                                     "□□□□□□□□□□",
                                     "□□□□□□□□□□",
                                     "□75□□□□12□",
                                     "□□□□3□□□□□"};
        }
        if (mQuestionNo == 4) {
            wkStrArr = new string[] {"□□□□□□□□□□",
                                     "□4□□□□□□□□",
                                     "□67□□□32□□",
                                     "8□□□□□□□□□",
                                     "□□3□□1□□□□",
                                     "□□4□□□2□□□",
                                     "□□5□□□□1□□",
                                     "□□□□□□□□□6",
                                     "□□□□□□□□□5",
                                     "□□□8□□□□□7"};
        }
        if (mQuestionNo == 5) {
            wkStrArr = new string[] {"□□□□□□□□□□□□□□□",
                                     "□□□□□□□□□□□□□□□",
                                     "□□□12□□□8765□□□",
                                     "□□□43□□□□□□6□□□",
                                     "□□□□□□□□□□□5□□□",
                                     "□□□□□□□□□□□4□□□",
                                     "□□2C□□□□9□□□□□□",
                                     "□□D□□□□□□□□□3□□",
                                     "□□□□□□F□□□□FE□□",
                                     "□□□1□□□□□□□□□□□",
                                     "□□□A□□□□□□□□□□□",
                                     "□□□B□□□□□□98□□□",
                                     "□□□CDEB□□□A7□□□",
                                     "□□□□□□□□□□□□□□□",
                                     "□□□□□□□□□□□□□□□"};
        }
        UB = wkStrArr.GetUpperBound(0);

        mQuestionArr = new char[UB + 1, UB + 1];
        for (int Y = 0; Y <= UB; Y++)
            for (int X = 0; X <= UB; X++)
                mQuestionArr[Y, X] = wkStrArr[X][Y];
    }

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

        //問題を定義
        QuestionDef();

        //リンクの開始座標と終了座標を取得
        DeriveLinkInfoArr();

        //深さ優先探索でリンクを作成
        ExecDFS();

        Console.WriteLine("経過時間={0}", sw.Elapsed);
    }

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

    struct JyoutaiDefDFS
    {
        internal char[,] BanArr;
        internal int Level;
        internal Dictionary<char, string> LinkInfoDict;
        internal HashSet<char> IsGoalSet;
    }

    //深さ優先探索でリンクを作成
    static void ExecDFS()
    {
        var Stk = new Stack<JyoutaiDefDFS>();
        JyoutaiDefDFS WillPush;

        WillPush.BanArr = mQuestionArr;
        WillPush.Level = 0;
        WillPush.LinkInfoDict = new Dictionary<char, string>();
        foreach (LinkInfoDef EachLinkInfo in mLinkInfoArr) {
            string PosStr = string.Format("{0},{1}", EachLinkInfo.StaX, EachLinkInfo.StaY);
            WillPush.LinkInfoDict[EachLinkInfo.ID] = PosStr;
        }
        WillPush.IsGoalSet = new HashSet<char>();
        Stk.Push(WillPush);

        int PopCnt = 0;
        while (Stk.Count > 0) {
            JyoutaiDefDFS Popped = Stk.Pop();

            PopCnt++;
            if (PopCnt % 10000 == 0) {
                Console.WriteLine("Pop回数={0}", PopCnt);
                PrintBan(Popped.BanArr);
            }

            //クリア判定
            if (Popped.IsGoalSet.Count == mLinkInfoArr.Length) {
                Console.WriteLine("解を発見");
                PrintBan(Popped.BanArr);
                break;
            }

            //リンクを伸ばす文字を決める
            char CurrID = '\0';
            int I = Popped.Level % mLinkInfoArr.Length;
            while (true) {
                if (I > mLinkInfoArr.GetUpperBound(0))
                    I = 0;

                CurrID = mLinkInfoArr[I].ID;
                if (Popped.IsGoalSet.Contains(CurrID) == false)
                    break;

                I++;
            }

            string[] SplitStr = Popped.LinkInfoDict[CurrID].Split(',');
            int CurrX = int.Parse(SplitStr[0]);
            int CurrY = int.Parse(SplitStr[1]);

            int EndX = -1, EndY = -1;
            foreach (LinkInfoDef EachLinkInfo in mLinkInfoArr) {
                if (EachLinkInfo.ID == CurrID) {
                    EndX = EachLinkInfo.EndX;
                    EndY = EachLinkInfo.EndY;
                    break;
                }
            }

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

                if (Popped.BanArr[pNewX, pNewY] != '□') return;

                //ゴールでも直近でもないマスと隣接していたら枝切り
                PointDef PrevPos = new PointDef() { X = CurrX, Y = CurrY };
                PointDef CurrPos = new PointDef() { X = pNewX, Y = pNewY };
                PointDef GoalPos = new PointDef() { X = EndX, Y = EndY };
                List<char> wk4KinbouList = Derive4KinbouList(Popped.BanArr, PrevPos, CurrPos, GoalPos);
                if (wk4KinbouList.Contains(CurrID)) return;

                WillPush.BanArr = (char[,])Popped.BanArr.Clone();
                WillPush.BanArr[pNewX, pNewY] = CurrID;

                //リンクが凹の形なら枝切り
                if (HasHekomiLink(WillPush.BanArr)) return;

                //UnionFindで枝切り判定
                if (WillEdakiriUnionFind(WillPush.BanArr, CurrID, pNewX, pNewY)) return;

                WillPush.Level = Popped.Level + 1;
                WillPush.LinkInfoDict = new Dictionary<char, string>(Popped.LinkInfoDict);
                WillPush.LinkInfoDict[CurrID] = string.Format("{0},{1}", pNewX, pNewY);

                //クリア判定(終点の4近傍にいたら探索終了)
                bool IsOK = false;
                if (pNewX == EndX && pNewY == EndY - 1) IsOK = true;
                if (pNewX == EndX && pNewY == EndY + 1) IsOK = true;
                if (pNewX == EndX - 1 && pNewY == EndY) IsOK = true;
                if (pNewX == EndX + 1 && pNewY == EndY) IsOK = true;
                if (IsOK) {
                    WillPush.IsGoalSet = new HashSet<char>(Popped.IsGoalSet);
                    WillPush.IsGoalSet.Add(CurrID);
                }
                else WillPush.IsGoalSet = Popped.IsGoalSet;

                Stk.Push(WillPush);
            };

            PushSyori(CurrX, CurrY - 1);
            PushSyori(CurrX, CurrY + 1);
            PushSyori(CurrX - 1, CurrY);
            PushSyori(CurrX + 1, CurrY);
        }
    }

    //UnionFindで枝切り判定
    static bool WillEdakiriUnionFind(char[,] pBanArr, char pID, int pCurrX, int pCurrY)
    {
        foreach (LinkInfoDef EachLinkInfo in mLinkInfoArr) {
            if (EachLinkInfo.ID != pID) continue;
            if (ExecUnionFind(pBanArr, EachLinkInfo, pCurrX, pCurrY) == false)
                return true;
        }
        return false;
    }

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

    //UnionFindでリンクを作成可能かを返す
    static bool ExecUnionFind(char[,] pBanArr, LinkInfoDef pLinkInfo, int pCurrX, int pCurrY)
    {
        var Stk = new Stack<JyoutaiDefUnionFind>();
        JyoutaiDefUnionFind WillPush;
        WillPush.CurrX = pCurrX;
        WillPush.CurrY = pCurrY;
        Stk.Push(WillPush);

        var VisitedSet = new HashSet<int>();
        VisitedSet.Add(PointToInt(WillPush.CurrX, WillPush.CurrY));
        while (Stk.Count > 0) {
            JyoutaiDefUnionFind Popped = Stk.Pop();

            //クリア判定(終点の4近傍にいたら探索終了)
            bool IsOK = false;
            if (Popped.CurrX == pLinkInfo.EndX && Popped.CurrY == pLinkInfo.EndY - 1) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX && Popped.CurrY == pLinkInfo.EndY + 1) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX - 1 && Popped.CurrY == pLinkInfo.EndY) IsOK = true;
            if (Popped.CurrX == pLinkInfo.EndX + 1 && Popped.CurrY == pLinkInfo.EndY) IsOK = true;
            if (IsOK) return true;

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

                if (pBanArr[pNewX, pNewY] != '□') return;

                //再訪防止
                if (VisitedSet.Add(PointToInt(pNewX, pNewY))) {
                    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);
        }
        //Console.WriteLine("{0}へのリンクを貼れませんでした", pLinkInfo.ID);
        //Console.WriteLine("pCurrX={0},pCurrY={1}", pCurrX, pCurrY);
        //PrintBan(pBanArr);
        return false;
    }

    //リンクが凹の形なら枝切り
    static bool HasHekomiLink(char[,] pBanArr)
    {
        for (int X = 0; X <= UB; X++) {
            for (int Y = 0; Y <= UB; Y++) {
                if (pBanArr[X, Y] != '□') continue;

                var NumList = new List<char>();

                Action<int, int> wkAct = (pTargetX, pTargetY) =>
                {
                    if (pTargetX < 0 || UB < pTargetX) return;
                    if (pTargetY < 0 || UB < pTargetY) return;

                    if (pBanArr[pTargetX, pTargetY] == '□') return;

                    NumList.Add(pBanArr[pTargetX, pTargetY]);
                };

                wkAct(X, Y - 1);
                wkAct(X, Y + 1);
                wkAct(X - 1, Y);
                wkAct(X + 1, Y);

                if (NumList.GroupBy(A => A).Any(A => A.Count() >= 3))
                    return true;
            }
        }
        return false;
    }

    //4近傍の値のList(直近とゴールは除外)を返す
    static List<char> Derive4KinbouList(char[,] pBanArr,
        PointDef pPrevPoint, PointDef pCurrPoint, PointDef pGoalPoint)
    {
        var WillReturn = new List<char>();

        int CurrX = pCurrPoint.X;
        int CurrY = pCurrPoint.Y;

        Action<int, int> wkAct = (pTargetX, pTargetY) =>
        {
            if (pTargetX < 0 || UB < pTargetX) return;
            if (pTargetY < 0 || UB < pTargetY) return;

            if (pTargetX == pPrevPoint.X && pTargetY == pPrevPoint.Y)
                return;
            if (pTargetX == pGoalPoint.X && pTargetY == pGoalPoint.Y)
                return;

            WillReturn.Add(pBanArr[pTargetX, pTargetY]);
        };

        wkAct(CurrX, CurrY - 1);
        wkAct(CurrX, CurrY + 1);
        wkAct(CurrX - 1, CurrY);
        wkAct(CurrX + 1, CurrY);

        return WillReturn;
    }

    struct LinkInfoDef
    {
        internal char ID;
        internal int StaX;
        internal int StaY;
        internal int EndX;
        internal int EndY;
    }
    static LinkInfoDef[] mLinkInfoArr;

    //リンクの開始座標と終了座標を取得
    static void DeriveLinkInfoArr()
    {
        var LinkInfoList = new List<LinkInfoDef>();
        char[] IDArr = mQuestionArr.Cast<char>().Where(A => A != '□').Distinct().ToArray();
        Array.Sort(IDArr);

        LinkInfoDef WillAdd;
        foreach (char EachID in IDArr) {
            WillAdd.ID = EachID;
            WillAdd.StaX = WillAdd.StaY = -1;
            WillAdd.EndX = WillAdd.EndY = -1;

            int HitCnt = 0;
            for (int Y = 0; Y <= UB; Y++) {
                for (int X = 0; X <= UB; X++) {
                    if (mQuestionArr[X, Y] != EachID) continue;
                    HitCnt++;
                    if (HitCnt == 1) {
                        WillAdd.StaX = X; WillAdd.StaY = Y;
                    }
                    if (HitCnt == 2) {
                        WillAdd.EndX = X; WillAdd.EndY = Y;
                        LinkInfoList.Add(WillAdd);
                    }
                }
            }
        }
        mLinkInfoArr = LinkInfoList.ToArray();
    }

    //X座標とY座標のペアをInt型に変換
    static int PointToInt(int pX, int pY)
    {
        return pX * (UB + 1) + pY;
    }

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


実行結果



解説


Cマガ電脳クラブ(第161回) ロード・オブ・ザ・リンク