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

13-13 お絵かきロジック

問題

世界文化社のお絵かきロジックを解きます。

問1
    

問2
    


ソース

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

class Program
{
    //ラインごとの候補の構造体
    struct KouhoInfoDef
    {
        internal bool IsXZahyou;
        internal int Zahyou;
        internal List<char[]> KouhoArrList;
    }

    static List<int[]> QuestionXNumArrList;
    static List<int[]> QuestionYNumArrList;
    static int UB_X;
    static int UB_Y;

    static KouhoInfoDef[] KouhoInfoArr; //ラインごとの候補の配列

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

    static void Main()
    {
        List<int[]> Q01_XNumArrList = new List<int[]>();
        Q01_XNumArrList.Add(new int[] { 3 });
        Q01_XNumArrList.Add(new int[] { 4 });
        Q01_XNumArrList.Add(new int[] { 4 });
        Q01_XNumArrList.Add(new int[] { 11 });
        Q01_XNumArrList.Add(new int[] { 2, 1 });
        Q01_XNumArrList.Add(new int[] { 1, 2, 2 });
        Q01_XNumArrList.Add(new int[] { 2, 1, 3 });
        Q01_XNumArrList.Add(new int[] { 1, 2, 4 });
        Q01_XNumArrList.Add(new int[] { 1, 1, 3 });
        Q01_XNumArrList.Add(new int[] { 11 });

        List<int[]> Q01_YNumArrList = new List<int[]>();
        Q01_YNumArrList.Add(new int[] { 4 });
        Q01_YNumArrList.Add(new int[] { 3, 1 });
        Q01_YNumArrList.Add(new int[] { 2, 1 });
        Q01_YNumArrList.Add(new int[] { 1, 3 });
        Q01_YNumArrList.Add(new int[] { 1, 3, 1 });
        Q01_YNumArrList.Add(new int[] { 3, 1 });
        Q01_YNumArrList.Add(new int[] { 1, 1 });
        Q01_YNumArrList.Add(new int[] { 1, 1 });
        Q01_YNumArrList.Add(new int[] { 1, 1 });
        Q01_YNumArrList.Add(new int[] { 1, 3 });
        Q01_YNumArrList.Add(new int[] { 2, 4 });
        Q01_YNumArrList.Add(new int[] { 3, 4 });
        Q01_YNumArrList.Add(new int[] { 4, 3 });
        Q01_YNumArrList.Add(new int[] { 3 });
        Q01_YNumArrList.Add(new int[] { 2 });

        List<int[]> Q02_XNumArrList = new List<int[]>();
        Q02_XNumArrList.Add(new int[] { 4 });
        Q02_XNumArrList.Add(new int[] { 2, 8 });
        Q02_XNumArrList.Add(new int[] { 2, 2, 3 });
        Q02_XNumArrList.Add(new int[] { 3, 1, 1, 2 });
        Q02_XNumArrList.Add(new int[] { 1, 2, 2, 1, 2 });
        Q02_XNumArrList.Add(new int[] { 1, 3, 1, 1 });
        Q02_XNumArrList.Add(new int[] { 1, 3, 4, 1 });
        Q02_XNumArrList.Add(new int[] { 1, 1, 1, 1 });
        Q02_XNumArrList.Add(new int[] { 1, 4, 2 });
        Q02_XNumArrList.Add(new int[] { 1, 4, 2 });
        Q02_XNumArrList.Add(new int[] { 1, 2, 2, 3 });
        Q02_XNumArrList.Add(new int[] { 3, 8 });
        Q02_XNumArrList.Add(new int[] { 2, 7 });
        Q02_XNumArrList.Add(new int[] { 2, 7 });
        Q02_XNumArrList.Add(new int[] { 4 });

        List<int[]> Q02_YNumArrList = new List<int[]>();
        Q02_YNumArrList.Add(new int[] { 9 });
        Q02_YNumArrList.Add(new int[] { 2, 2 });
        Q02_YNumArrList.Add(new int[] { 4, 4 });
        Q02_YNumArrList.Add(new int[] { 2, 7, 2 });
        Q02_YNumArrList.Add(new int[] { 1, 2, 2, 1 });
        Q02_YNumArrList.Add(new int[] { 1, 6, 1 });
        Q02_YNumArrList.Add(new int[] { 5, 3, 2 });
        Q02_YNumArrList.Add(new int[] { 2, 1, 4 });
        Q02_YNumArrList.Add(new int[] { 1, 1, 3 });
        Q02_YNumArrList.Add(new int[] { 1, 1, 3 });
        Q02_YNumArrList.Add(new int[] { 1, 4, 3 });
        Q02_YNumArrList.Add(new int[] { 1, 3 });
        Q02_YNumArrList.Add(new int[] { 2, 4 });
        Q02_YNumArrList.Add(new int[] { 4, 5 });
        Q02_YNumArrList.Add(new int[] { 10 });

        List<int[]> Q03_XNumArrList = new List<int[]>();
        Q03_XNumArrList.Add(new int[] { 1 });
        Q03_XNumArrList.Add(new int[] { 1 });
        Q03_XNumArrList.Add(new int[] { 0 });

        List<int[]> Q03_YNumArrList = new List<int[]>();
        Q03_YNumArrList.Add(new int[] { 1 });
        Q03_YNumArrList.Add(new int[] { 1 });
        Q03_YNumArrList.Add(new int[] { 0 });

        QuestionXNumArrList = Q01_XNumArrList;
        QuestionYNumArrList = Q01_YNumArrList;
        UB_X = QuestionXNumArrList.Count - 1;
        UB_Y = QuestionYNumArrList.Count - 1;

        //第1フェーズ ラインごとの候補を列挙
        KouhoInfoArr = DeriveKouhoInfoArr();

        //デバッグ用 ラインごとの候補の出力
        //DebugPrintKouhoInfo();

        char[,] BanArr = new char[UB_X + 1, UB_Y + 1];
        for (int X = 0; X <= UB_X; X++) {
            for (int Y = 0; Y <= UB_Y; Y++) {
                BanArr[X, Y] = ' ';
            }
        }

        //第2フェーズ ラインごとの全候補との共通部分を設定
        KakuteiTansaku(BanArr);

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

    //第1フェーズ ラインごとの候補を列挙
    static KouhoInfoDef[] DeriveKouhoInfoArr()
    {
        var WillReturnList = new List<KouhoInfoDef>();

        for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
            KouhoInfoDef WillAdd;
            WillAdd.IsXZahyou = true;
            WillAdd.Zahyou = LoopX;
            WillAdd.KouhoArrList = DeriveKouhoInfoArrHelper(WillAdd.IsXZahyou, WillAdd.Zahyou);
            WillReturnList.Add(WillAdd);
        }

        for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
            KouhoInfoDef WillAdd;
            WillAdd.IsXZahyou = false;
            WillAdd.Zahyou = LoopY;
            WillAdd.KouhoArrList = DeriveKouhoInfoArrHelper(WillAdd.IsXZahyou, WillAdd.Zahyou);
            WillReturnList.Add(WillAdd);
        }

        return WillReturnList.ToArray();
    }

    //ラインごとの候補を列挙する探索の状態Def
    struct JyotaiDef1
    {
        internal int LineNumArrP;
        internal char[] LineKouhoArr;
    }

    //第1フェーズ ラインごとの候補を列挙
    static List<char[]> DeriveKouhoInfoArrHelper(bool pIsXZahyou, int pZahyou)
    {
        var WillReturnList = new List<char[]>();

        int[] LineNumArr = (pIsXZahyou ? QuestionXNumArrList[pZahyou] :
                                         QuestionYNumArrList[pZahyou]);
        int UB = (pIsXZahyou ? UB_Y : UB_X); //固定する軸がX軸なら、Y座標のUBとなる

        //0だけの特殊ケース
        if (LineNumArr[0] == 0) {
            char[] wkArr = new char[UB + 1];
            for (int I = 0; I <= UB; I++) wkArr[I] = '/';
            WillReturnList.Add(wkArr);
            return WillReturnList;
        }

        var stk = new Stack<JyotaiDef1>();
        JyotaiDef1 WillPush;
        WillPush.LineNumArrP = 0;
        WillPush.LineKouhoArr = new char[UB + 1];
        for (int I = 0; I <= UB; I++) WillPush.LineKouhoArr[I] = ' ';
        stk.Push(WillPush);

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

            //終了判定
            int wkLineKouhoArrP = Array.FindIndex(Popped.LineKouhoArr, A => A == ' ');
            if (wkLineKouhoArrP < 0) {
                if (Popped.LineNumArrP > LineNumArr.GetUpperBound(0))
                    WillReturnList.Add(Popped.LineKouhoArr);
                continue;
            }

            //黒マスを埋めるケース
            if (Popped.LineNumArrP <= LineNumArr.GetUpperBound(0)) {
                int wkNum = LineNumArr[Popped.LineNumArrP];
                int RestMasuCnt = UB - wkLineKouhoArrP + 1;

                //黒マスを埋められなければ無効な状態
                if (RestMasuCnt < wkNum) continue;

                //黒マスで埋める
                WillPush.LineKouhoArr = (char[])Popped.LineKouhoArr.Clone();
                for (int I = wkLineKouhoArrP; I <= wkLineKouhoArrP + wkNum - 1; I++) {
                    WillPush.LineKouhoArr[I] = '*';
                }

                //残りマスがあったら確定空白となる
                int NewLineInd = wkLineKouhoArrP + wkNum;
                if (NewLineInd <= UB)
                    WillPush.LineKouhoArr[NewLineInd] = '/';

                //Push処理
                WillPush.LineNumArrP = Popped.LineNumArrP + 1;
                stk.Push(WillPush);
            }

            //確定空白とするケース
            WillPush.LineKouhoArr = (char[])Popped.LineKouhoArr.Clone();
            WillPush.LineKouhoArr[wkLineKouhoArrP] = '/';
            WillPush.LineNumArrP = Popped.LineNumArrP;
            stk.Push(WillPush);
        }

        return WillReturnList;
    }

    //デバッグ用 ラインごとの候補の出力
    static void DebugPrintKouhoInfo()
    {
        foreach (KouhoInfoDef EachKouhoInfo in KouhoInfoArr) {
            Console.WriteLine("■■■■■■■■■■■■■");
            Console.WriteLine("{0}{1}",
                EachKouhoInfo.IsXZahyou ? "X座標" : "Y座標", EachKouhoInfo.Zahyou);

            for (int I = 0; I <= EachKouhoInfo.KouhoArrList.Count - 1; I++) {
                Console.WriteLine("候補{0}", I + 1);
                Array.ForEach<char>(EachKouhoInfo.KouhoArrList[I], A => Console.Write(A));
                Console.WriteLine();
            }
        }
    }

    //第2フェーズ ラインごとの全候補との共通部分を設定し
    //有効な盤面かを返す
    static bool KakuteiTansaku(char[,] pBanArr)
    {
        while (true) {
            char[,] PrevBanArr = (char[,])pBanArr.Clone();

            for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                char[] wkLineArr = new char[UB_Y + 1];
                for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                    wkLineArr[LoopY] = pBanArr[LoopX, LoopY];
                }
                //ラインごとの全候補での共通部分を設定
                bool IsSet;
                if (KakuteiTansakuHelper(wkLineArr, true, LoopX, out IsSet) == false)
                    return false;
                if (IsSet == false) continue;
                for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                    pBanArr[LoopX, LoopY] = wkLineArr[LoopY];
                }
            }

            for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                char[] wkLineArr = new char[UB_X + 1];
                for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                    wkLineArr[LoopX] = pBanArr[LoopX, LoopY];
                }
                //ラインごとの全候補での共通部分を設定
                bool IsSet;
                if (KakuteiTansakuHelper(wkLineArr, false, LoopY, out IsSet) == false)
                    return false;
                if (IsSet == false) continue;
                for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                    pBanArr[LoopX, LoopY] = wkLineArr[LoopX];
                }
            }

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

    //確定探索2 ラインごとの全候補での共通部分を設定し
    //有効な盤面かを返す
    static bool KakuteiTansakuHelper(char[] pLineArr, bool pIsXZahyou, int pZahyou, out bool pIsSet)
    {
        pIsSet = false;

        //ラインの現在の状態を引数として候補の配列のListを返す
        List<char[]> wkKouhoArrList = DeriveKouhoArrList(pLineArr, pIsXZahyou, pZahyou);

        if (wkKouhoArrList.Count == 0) return false;

        //全候補での共通部分を設定
        for (int I = 0; I <= pLineArr.GetUpperBound(0); I++) {
            if (pLineArr[I] != ' ') continue;

            IEnumerable<char> wkEnum = wkKouhoArrList.Select(A => A[I]).Distinct();
            if (wkEnum.Count() == 1) {
                pLineArr[I] = wkEnum.First();
                pIsSet = true;
            }
        }
        return true;
    }

    //ラインの現在の状態を引数として候補の配列のListを返す
    static List<char[]> DeriveKouhoArrList(char[] pLineArr, bool pIsXZahyou, int pZahyou)
    {
        KouhoInfoDef KouhoInfo = Array.Find(KouhoInfoArr, A => A.IsXZahyou == pIsXZahyou
                                                            && A.Zahyou == pZahyou);

        List<char[]> wkKouhoArrList = new List<char[]>();
        KouhoInfo.KouhoArrList.ForEach(A => wkKouhoArrList.Add((char[])A.Clone()));

        //空白以外で差異がある候補をRemove
        for (int I = wkKouhoArrList.Count - 1; 0 <= I; I--) {
            bool WillRemove = false;
            for (int J = 0; J <= pLineArr.GetUpperBound(0); J++) {
                if (pLineArr[J] == ' ') continue;
                if (pLineArr[J] != wkKouhoArrList[I][J]) {
                    WillRemove = true;
                    break;
                }
            }
            if (WillRemove) wkKouhoArrList.RemoveAt(I);
        }
        return wkKouhoArrList;
    }

    //仮置きする深さ優先探索の状態Def
    struct JyotaiDef3
    {
        internal char[,] BanArr;
    }

    //第3フェーズ 確定探索付の深さ優先探索
    static void ExecDFS(char[,] pBanArr)
    {
        var stk = new Stack<JyotaiDef3>();
        JyotaiDef3 WillPush;
        WillPush.BanArr = pBanArr;

        stk.Push(WillPush);

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

            //空白マスのあるX座標を求める
            int SpaceXZahyou = DeriveSpaceXZahyou(Popped.BanArr);

            //クリア判定
            if (SpaceXZahyou == -1) {
                Console.WriteLine("解を発見。経過時間={0}", sw.Elapsed);
                PrintAnswer(Popped.BanArr);
                continue;
            }

            char[] wkLineArr = new char[UB_Y + 1];
            for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                wkLineArr[LoopY] = pBanArr[SpaceXZahyou, LoopY];
            }

            //ラインの現在の状態を引数として候補の配列のListを返す
            List<char[]> wkKouhoArrList = DeriveKouhoArrList(wkLineArr, true, SpaceXZahyou);

            foreach (char[] EachKouhoArr in wkKouhoArrList) {
                WillPush.BanArr = (char[,])Popped.BanArr.Clone();
                for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                    WillPush.BanArr[SpaceXZahyou, LoopY] = EachKouhoArr[LoopY];
                }
                if (KakuteiTansaku(WillPush.BanArr))
                    stk.Push(WillPush);
            }
        }
    }

    //空白マスのあるX座標を求める
    static int DeriveSpaceXZahyou(char[,] pBanArr)
    {
        for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
            for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                if (pBanArr[LoopX, LoopY] == ' ') return LoopX;
            }
        }
        return -1;
    }

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


実行結果

解を発見。経過時間=00:00:00.1114803
××××××■■■■
××××■■■××■
×××■■××××■
×××■×××■■■
×××■×■■■×■
×××■■■×××■
×××■×××××■
×××■×××××■
×××■×××××■
×××■×××■■■
××■■××■■■■
×■■■×■■■■×
■■■■×■■■××
■■■×××××××
■■××××××××


解説

最初にラインごとの候補を列挙しておき、
確定探索で、ラインごとの候補から既存の盤面を異なる候補を除外した上で、
残りの全候補で共通する配置を確定マスとするアルゴリズムを使ってます。