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

Cマガ電脳クラブ(第016回) ぶらさがり覆面算

問題

今回はちょっと変わった覆面算(Fig.1)だ。
文字が同じならば隠されている数字は同じで、文字が違えば数字が違うのは、普通の覆面算と同じである。
ただし、* にはどの数字を当てはめてもよい。
さて、ここで肝心なのは、「横に隣り合った数の差 (絶対値) が必ずその下にある」ということだ。
Fig.2に簡単な復元例を示す。Fig.1を復元してください。

Fig.1
C C L A N G U A G E
 * * * * * * * * *
  * * * * * * * *
   * * * * * * *
    * * * * * *
     C M A G A
      * * * *
       * * *
        * *
         *

Fig.2
M A G I C   3 1 2 5 9
 G A M E     2 1 3 4
  * * *       1 2 1
   * *         1 1
    *           0


ソース

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

class Program
{
    const bool IsSampleQuestion = false; //サンプル問題ならTrue

    const int Level1Length = (IsSampleQuestion ? 5 : 10);
    const int NodeCnt = Level1Length * (Level1Length + 1) / 2; //等差数列の和の公式

    static void Main()
    {
        List<int[]> TopNodesCombiList = DeriveTopNodeCombiList();

        foreach (int[] AnyIntArr in TopNodesCombiList) {
            int[] wkArrP = FillAbs(AnyIntArr);

            if (IsSampleQuestion && IsOK_Sample(wkArrP)) {
                Console.WriteLine("解を発見。");
                PrintAnswer(wkArrP);
            }
            else if (!IsSampleQuestion && IsOK_Honmon(wkArrP)) {
                Console.WriteLine("解を発見。");
                PrintAnswer(wkArrP);
            }
        }
    }

    //最上位のノードの数字の組み合わせをInt配列のListジェネリックで返す
    static List<int[]> DeriveTopNodeCombiList()
    {
        var WillReturnList = new List<int[]>();
        var stk = new Stack<List<int>>();

        for (int I = 0; I <= Level1Length; I++) {
            stk.Push(new List<int> { I });
        }

        while (stk.Count > 0) {
            List<int> PoppedList = stk.Pop();

            if (PoppedList.Count == Level1Length) {
                WillReturnList.Add(PoppedList.ToArray());
                continue;
            }

            for (int I = 0; I <= 9; I++) {
                if (IsSampleQuestion) {
                    if (IsValidTop_Sample(PoppedList, I) == false) continue;
                }
                if (!IsSampleQuestion) {
                    if (IsValidTop_Honmon(PoppedList, I) == false) continue;
                }

                stk.Push(new List<int>(PoppedList) { I });
            }
        }
        return WillReturnList;
    }

    //有効なトップノードかのチェック(サンプル問題用)
    static bool IsValidTop_Sample(List<int> pList, int pAddVal)
    {
        int AddP = pList.Count;

        //文字の重複チェック
        if (IsSampleQuestion && 1 <= AddP && AddP <= 4) {
            if (pList.Contains(pAddVal)) return false;
        }
        return true;
    }

    //有効なトップノードかのチェック(本問用)
    static bool IsValidTop_Honmon(List<int> pList, int pAddVal)
    {
        int AddP = pList.Count;

        //文字の一致チェック
        if (AddP == 1 && pList[0] != pAddVal) return false;
        if (AddP == 7 && pList[3] != pAddVal) return false;
        if (AddP == 8 && pList[5] != pAddVal) return false;

        //文字の重複チェック
        if (2 <= AddP && AddP <= 6 || AddP == 9) {
            if (pList.Contains(pAddVal)) return false;
        }

        return true;
    }

    //トップノードの配列を受け取り、下位ノードを絶対値で埋めた配列を返す
    static int[] FillAbs(int[] pTopNodes)
    {
        int[] FilledArr = new int[NodeCnt];
        Array.Copy(pTopNodes, FilledArr, Level1Length);

        int CurrP = pTopNodes.GetUpperBound(0) + 1;
        for (int Y = Level1Length - 1; Y >= 1; Y--) {
            for (int X = 1; X <= Y; X++) {
                int WK1 = FilledArr[CurrP - Y - 1];
                int WK2 = FilledArr[CurrP - Y];
                FilledArr[CurrP++] = Math.Abs(WK1 - WK2);
            }
        }
        return FilledArr;
    }

    //絶対値を埋めたツリーが解かのチェック(サンプル問題用)
    static bool IsOK_Sample(int[] pArr)
    {
        if (pArr[2] != pArr[5]) return false; //2レベル目のG
        if (pArr[1] != pArr[6]) return false; //2レベル目のA
        if (pArr[0] != pArr[7]) return false; //2レベル目のM

        //2レベル目のE
        if (Array.IndexOf<int>(pArr, pArr[8], 0, 8) >= 0) return false;

        return true;
    }

    //絶対値を埋めたツリーが解かのチェック(サンプル問題用)
    static bool IsOK_Honmon(int[] pArr)
    {
        if (pArr[40] != pArr[0]) return false; //5レベル目のC
        if (pArr[42] != pArr[3]) return false; //5レベル目のA
        if (pArr[43] != pArr[5]) return false; //5レベル目のG
        if (pArr[44] != pArr[3]) return false; //5レベル目のA

        //5レベル目のM
        if (Array.IndexOf<int>(pArr, pArr[41], 0, Level1Length) >= 0) return false;

        return true;
    }

    //解を出力
    static void PrintAnswer(int[] pArr)
    {
        int CurrP = 0, SPCnt = 0;
        for (int XCnt = Level1Length; 1 <= XCnt; XCnt--) { //出力する行のループ
            Console.Write(new string(' ', SPCnt++));//空白を出力
            for (int I = 1; I <= XCnt; I++) { //1行に対する出力のループ
                Console.Write("{0} ", pArr[CurrP++]);
            }
            Console.WriteLine();
        }
    }
}


実行結果

解を発見。
0 0 7 3 8 2 4 3 2 9
 0 7 4 5 6 2 1 1 7
  7 3 1 1 4 1 0 6
   4 2 0 3 3 1 6
    2 2 3 0 2 5
     0 1 3 2 3
      1 2 1 1
       1 1 0
        0 1
         1


解説

下記のように、CCLANGまで確定すれば、
5レベル目のCが確定するので5レベル目のCで枝切りしてもいいです。
C C L A N G
 * * * * *
  * * * *
   * * *
    * *
     C
同様に、
CCLANGUまで確定すれば、5レベル目のMで枝切り
CCLANGUAまで確定すれば、5レベル目のAで枝切り
といった枝切りも可能です。