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

Cマガ電脳クラブ(第037回) カラーボックス

問題

Fig.1のように8個の立方体がある。各面には図に示された色が塗ってある。
マクマホンの発明で欧州で市販されたものだ。

この8個を2×2×2の立方体に積むのだが、条件がある。
 (1)できる大きな立方体の各面は、それぞれ同色にそろえる。
 (2)隣り合う立方体の接触する面は、それぞれ同じ色でなければならない。
解の総数を見つけてほしい。

Fig.1 カラーボックス


ソース

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

class Program
{
    //ダイス番号(1〜8)と、6面の配置候補の配列のList
    static Dictionary<int, List<char[]>> Use6MenArrListDict =
        new Dictionary<int, List<char[]>>();

    struct JyoutaiDef
    {
        internal int Level;
        internal List<int> SetDiceNo; //配置したダイス番号
        internal List<char[]> Set6MenArrList; //配置したダイスの展開図のList
    }

    static void Main()
    {
        DeriveUse6MenArrListDict();

        var stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;

        WillPush.Level = 0;
        WillPush.SetDiceNo = new List<int>();
        WillPush.Set6MenArrList = new List<char[]>();
        stk.Push(WillPush);

        int AnswerCnt = 0;

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

            if (Popped.Level == 8) {
                Console.WriteLine("解{0}を発見", ++AnswerCnt);
                PrintAnswer(Popped);
                continue;
            }

            WillPush.Level = Popped.Level + 1;
            foreach (var EachPair in Use6MenArrListDict) {
                //最初に配置するダイスは、固定で1番とする
                if (WillPush.Level == 1 && EachPair.Key != 1) continue;

                //回転解の除外で
                //上段右後のダイス番号 < 上段左前のダイス番号
                if (WillPush.Level == 3)
                    if (Popped.SetDiceNo[1] < EachPair.Key == false)
                        continue;

                if (Popped.SetDiceNo.Contains(EachPair.Key)) continue;

                foreach (char[] EachKouhoArr in EachPair.Value) {
                    WillPush.SetDiceNo = new List<int>(Popped.SetDiceNo) { EachPair.Key };
                    WillPush.Set6MenArrList = new List<char[]>(Popped.Set6MenArrList);
                    WillPush.Set6MenArrList.Add(EachKouhoArr);
                    if (IsValid(WillPush.Set6MenArrList) == false) continue;
                    stk.Push(WillPush);
                }
            }
        }
    }

    //8つのダイスの、使用する6面の候補を求める
    static void DeriveUse6MenArrListDict()
    {
        //この展開図と配列の添字を対応させて、8つのダイスの展開図を定義
        // 0
        //123
        // 4
        // 5

        var wk6MenArrList = new List<char[]>();
        wk6MenArrList.Add(new char[] { '白', '黄', '青', '赤', '緑', '黒' });
        wk6MenArrList.Add(new char[] { '黄', '白', '赤', '緑', '青', '黒' });
        wk6MenArrList.Add(new char[] { '緑', '赤', '黄', '白', '青', '黒' });
        wk6MenArrList.Add(new char[] { '黄', '緑', '赤', '黒', '青', '白' });
        wk6MenArrList.Add(new char[] { '黄', '緑', '黒', '青', '白', '赤' });
        wk6MenArrList.Add(new char[] { '黒', '白', '緑', '青', '黄', '赤' });
        wk6MenArrList.Add(new char[] { '緑', '黒', '白', '青', '赤', '黄' });
        wk6MenArrList.Add(new char[] { '黄', '黒', '青', '緑', '赤', '白' });

        for (int I = 0; I <= wk6MenArrList.Count - 1; I++) {
            Use6MenArrListDict[I + 1] = DeriveUse6MenArrList(wk6MenArrList[I]);
        }
    }

    //1つのダイスの、配置の候補を求める(ダイス1つにつき48通り)
    static List<char[]> DeriveUse6MenArrList(char[] p6MenArr)
    {
        var WillReturn = new List<char[]>();

        Action<int, int> wkAct = (pSokumenInd1, pSokumenInd2) =>
        {
            var wk4MenList = new List<char>(p6MenArr);

            //2と5以外の4面を使用する場合は、番号順にならないので入れ替え
            if (pSokumenInd1 == 2 && pSokumenInd2 == 5) {
                char wkChar = wk4MenList[3];
                wk4MenList[3] = wk4MenList[4];
                wk4MenList[4] = wkChar;
            }

            wk4MenList.RemoveAt(Math.Max(pSokumenInd1, pSokumenInd2));
            wk4MenList.RemoveAt(Math.Min(pSokumenInd1, pSokumenInd2));

            WillReturn.AddRange(DeriveUse4MenArrList(p6MenArr[pSokumenInd1],
                p6MenArr[pSokumenInd2], wk4MenList.ToArray()));
        };

        wkAct(0, 4); //0と4を側面としてダイスを配置
        wkAct(1, 3); //1と3を側面としてダイスを配置
        wkAct(2, 5); //2と5を側面としてダイスを配置
        return WillReturn;
    }

    //使用する4面の並び順を求める(16通り)
    static List<char[]> DeriveUse4MenArrList(char pSokumen1, char pSokumen2, char[] p4MenArr)
    {
        const int UB = 3;
        var WillReturn = new List<char[]>();

        //正順と逆順での共通処理
        Action<List<char>> CommonAct = pWKList =>
        {
            char[] WillAddArr = new char[6];
            WillAddArr[0] = pWKList[0];
            WillAddArr[2] = pWKList[1];
            WillAddArr[4] = pWKList[2];
            WillAddArr[5] = pWKList[3];
            WillAddArr[1] = pSokumen1; WillAddArr[3] = pSokumen2;
            WillReturn.Add(WillAddArr);

            WillAddArr = (char[])WillAddArr.Clone();
            WillAddArr[1] = pSokumen2; WillAddArr[3] = pSokumen1;
            WillReturn.Add(WillAddArr);
        };

        //正順の4つ
        for (int StaPos = 0; StaPos <= UB; StaPos++) {
            var WKList = new List<char>();
            for (int KasanP = 0; KasanP <= UB; KasanP++) {
                int TargetPos = StaPos + KasanP;
                if (TargetPos > UB) TargetPos -= (UB + 1);
                WKList.Add(p4MenArr[TargetPos]);
            }
            CommonAct(WKList);
        }

        //逆順の4つ
        for (int StaPos = 0; StaPos <= UB; StaPos++) {
            var WKList = new List<char>();
            for (int GenzanP = 0; GenzanP <= UB; GenzanP++) {
                int TargetPos = StaPos - GenzanP;
                if (TargetPos < 0) TargetPos += (UB + 1);
                WKList.Add(p4MenArr[TargetPos]);
            }
            CommonAct(WKList);
        }
        return WillReturn;
    }

    //上段左後  上段右後
    //上段左前  上段右前
    //下段左後  下段右後
    //下段左前  下段右前
    //の順番でダイスを配置するので、有効な状態かをチェックする
    static bool IsValid(List<char[]> pSet6MenArrList)
    {
        //上段右後の配置
        if (pSet6MenArrList.Count == 2) {
            //後面 = 上段左後のダイスの後面
            if (pSet6MenArrList[1][0] != pSet6MenArrList[0][0]) return false;

            //左面 = 上段左後のダイスの右面
            if (pSet6MenArrList[1][1] != pSet6MenArrList[0][3]) return false;

            //上面 = 上段左後のダイスの上面
            if (pSet6MenArrList[1][5] != pSet6MenArrList[0][5]) return false;
        }
        //上段左前の配置
        if (pSet6MenArrList.Count == 3) {
            //後面 = 上段左後のダイスの前面
            if (pSet6MenArrList[2][0] != pSet6MenArrList[0][4]) return false;

            //左面 = 上段左後のダイスの左面
            if (pSet6MenArrList[2][1] != pSet6MenArrList[0][1]) return false;

            //上面 = 上段左後のダイスの上面
            if (pSet6MenArrList[2][5] != pSet6MenArrList[0][5]) return false;
        }
        //上段右前の配置
        if (pSet6MenArrList.Count == 4) {
            //後面 = 上段右後のダイスの前面
            if (pSet6MenArrList[3][0] != pSet6MenArrList[1][4]) return false;

            //左面 = 上段左前のダイスの右面
            if (pSet6MenArrList[3][1] != pSet6MenArrList[2][3]) return false;

            //右面 = 上段右後のダイスの右面
            if (pSet6MenArrList[3][3] != pSet6MenArrList[1][3]) return false;

            //前面 = 上段左前のダイスの前面
            if (pSet6MenArrList[3][4] != pSet6MenArrList[2][4]) return false;

            //上面 = 上段左後のダイスの上面
            if (pSet6MenArrList[3][5] != pSet6MenArrList[0][5]) return false;
        }
        //下段左後の配置
        if (pSet6MenArrList.Count == 5) {
            //後面 = 上段左後のダイスの後面
            if (pSet6MenArrList[4][0] != pSet6MenArrList[0][0]) return false;

            //左面 = 上段左後のダイスの左面
            if (pSet6MenArrList[4][1] != pSet6MenArrList[0][1]) return false;

            //上面 = 上段左後のダイスの下面
            if (pSet6MenArrList[4][5] != pSet6MenArrList[0][2]) return false;
        }
        //下段右後の配置
        if (pSet6MenArrList.Count == 6) {
            //後面 = 上段左後のダイスの後面
            if (pSet6MenArrList[5][0] != pSet6MenArrList[0][0]) return false;

            //左面 = 下段左後のダイスの右面
            if (pSet6MenArrList[5][1] != pSet6MenArrList[4][3]) return false;

            //下面 = 下段左後のダイスの下面
            if (pSet6MenArrList[5][2] != pSet6MenArrList[4][2]) return false;

            //右面 = 上段右後のダイスの右面
            if (pSet6MenArrList[5][3] != pSet6MenArrList[1][3]) return false;

            //上面 = 上段右後のダイスの下面
            if (pSet6MenArrList[5][5] != pSet6MenArrList[1][2]) return false;
        }
        //下段左前の配置
        if (pSet6MenArrList.Count == 7) {
            //後面 = 下段左後のダイスの前面
            if (pSet6MenArrList[6][0] != pSet6MenArrList[4][4]) return false;

            //左面 = 上段左後のダイスの左面
            if (pSet6MenArrList[6][1] != pSet6MenArrList[0][1]) return false;

            //下面 = 下段左後のダイスの下面
            if (pSet6MenArrList[6][2] != pSet6MenArrList[4][2]) return false;

            //前面 = 上段左前のダイスの前面
            if (pSet6MenArrList[6][4] != pSet6MenArrList[2][4]) return false;

            //上面 = 上段左前のダイスの下面
            if (pSet6MenArrList[6][5] != pSet6MenArrList[2][2]) return false;
        }
        //下段右前の配置
        if (pSet6MenArrList.Count == 8) {
            //後面 = 下段右後のダイスの前面
            if (pSet6MenArrList[7][0] != pSet6MenArrList[5][4]) return false;

            //左面 = 下段左前のダイスの右面
            if (pSet6MenArrList[7][1] != pSet6MenArrList[6][3]) return false;

            //下面 = 下段左後のダイスの下面
            if (pSet6MenArrList[7][2] != pSet6MenArrList[4][2]) return false;

            //右面 = 上段右後のダイスの右面
            if (pSet6MenArrList[7][3] != pSet6MenArrList[1][3]) return false;

            //前面 = 上段左前のダイスの前面
            if (pSet6MenArrList[7][4] != pSet6MenArrList[2][4]) return false;

            //上面 = 上段右前のダイスの下面
            if (pSet6MenArrList[7][5] != pSet6MenArrList[3][2]) return false;
        }

        return true;
    }

    //配置したダイスを展開図で表示する
    //上段左後  上段右後
    //上段左前  上段右前
    //下段左後  下段右後
    //下段左前  下段右前
    //の順番で展開図を表示する
    static void PrintAnswer(JyoutaiDef pJyoutaiDef)
    {
        Action<int> PrintPairTenkaizu = pBaseSoeji =>
        {
            var sb = new System.Text.StringBuilder();
            sb.AppendFormat("Dice{0}    Dice{1}",
                pJyoutaiDef.SetDiceNo[pBaseSoeji], pJyoutaiDef.SetDiceNo[pBaseSoeji + 1]);
            sb.AppendLine();

            Action<int> CommonSyori = (pTargetMen) =>
            {
                sb.AppendFormat("  {0}      {1}",
                    pJyoutaiDef.Set6MenArrList[pBaseSoeji][pTargetMen],
                    pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][pTargetMen]);
                sb.AppendLine();
            };

            CommonSyori(0);

            sb.AppendFormat("{0}{1}{2}  {3}{4}{5}",
                pJyoutaiDef.Set6MenArrList[pBaseSoeji][1],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji][2],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji][3],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][1],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][2],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][3]);
            sb.AppendLine();

            CommonSyori(4);
            CommonSyori(5);

            Console.WriteLine(sb.ToString());
        };

        for (int I = 0; I <= pJyoutaiDef.SetDiceNo.Count - 1; I+=2) {
            if (I == 0) Console.WriteLine("上段左後と上段右後の展開図");
            if (I == 2) Console.WriteLine("上段左前と上段右前の展開図");
            if (I == 4) Console.WriteLine("下段左後と下段右後の展開図");
            if (I == 6) Console.WriteLine("下段左前と下段右前の展開図");
            PrintPairTenkaizu(I);
        }
    }
}


実行結果

解1を発見
上段左後と上段右後の展開図
Dice1    Dice2
  白      白
青赤黒  黒青赤
  緑      緑
  黄      黄

上段左前と上段右前の展開図
Dice8    Dice4
  緑      緑
青赤白  白青赤
  黒      黒
  黄      黄

下段左後と下段右後の展開図
Dice7    Dice5
  白      白
青緑黒  黒緑赤
  黄      黄
  赤      青

下段左前と下段右前の展開図
Dice6    Dice3
  黄      黄
青緑白  白緑赤
  黒      黒
  赤      青

解2を発見
上段左後と上段右後の展開図
Dice1    Dice2
  黄      黄
青緑黒  黒緑赤
  赤      青
  白      白

上段左前と上段右前の展開図
Dice7    Dice5
  赤      青
青黄黒  黒黄赤
  緑      緑
  白      白

下段左後と下段右後の展開図
Dice8    Dice4
  黄      黄
青黒白  白黒赤
  赤      青
  緑      緑

下段左前と下段右前の展開図
Dice6    Dice3
  赤      青
青黒白  白黒赤
  緑      緑
  黄      黄

解3を発見
上段左後と上段右後の展開図
Dice1    Dice2
  緑      緑
赤青黄  黄赤青
  白      白
  黒      黒

上段左前と上段右前の展開図
Dice7    Dice5
  白      白
赤青緑  緑赤青
  黄      黄
  黒      黒

下段左後と下段右後の展開図
Dice8    Dice4
  緑      緑
赤白黄  黄白青
  黒      黒
  青      赤

下段左前と下段右前の展開図
Dice6    Dice3
  黒      黒
赤白緑  緑白青
  黄      黄
  青      赤

解4を発見
上段左後と上段右後の展開図
Dice1    Dice7
  白      白
黄黒赤  赤黒緑
  緑      黄
  青      青

上段左前と上段右前の展開図
Dice8    Dice6
  緑      黄
黄白赤  赤白緑
  黒      黒
  青      青

下段左後と下段右後の展開図
Dice2    Dice5
  白      白
黄赤青  青赤緑
  緑      黄
  黒      黒

下段左前と下段右前の展開図
Dice4    Dice3
  緑      黄
黄赤青  青赤緑
  黒      黒
  白      白

解5を発見
上段左後と上段右後の展開図
Dice1    Dice2
  黒      黒
赤白黄  黄白青
  青      赤
  緑      緑

上段左前と上段右前の展開図
Dice8    Dice4
  青      赤
赤黒黄  黄黒青
  白      白
  緑      緑

下段左後と下段右後の展開図
Dice7    Dice5
  黒      黒
赤黄緑  緑黄青
  青      赤
  白      白

下段左前と下段右前の展開図
Dice6    Dice3
  青      赤
赤黄緑  緑黄青
  白      白
  黒      黒

解6を発見
上段左後と上段右後の展開図
Dice1    Dice7
  黒      黒
緑黄白  白緑黄
  青      青
  赤      赤

上段左前と上段右前の展開図
Dice8    Dice6
  青      青
緑黄黒  黒緑黄
  白      白
  赤      赤

下段左後と下段右後の展開図
Dice2    Dice5
  黒      黒
緑青白  白青黄
  赤      赤
  黄      緑

下段左前と下段右前の展開図
Dice4    Dice3
  赤      赤
緑青黒  黒青黄
  白      白
  黄      緑


解説

立方体の配置は24通りですが、
立方体の展開図から(展開図の反転を許可して)立方体を作成して配置するのは、48通りあります。

解の検証は、
各ダイスが展開図から作成可能な立方体であること。
各ダイスの6面が条件を満たすこと。
の2つをチェックしてます。

Cマガ電脳クラブ(第067回) 矢な八つ