トップページに戻る
次のC#のサンプルへ
前のC#のサンプルへ
Cマガ電脳クラブ(第065回) 24手
問題
Fig.1の左側のように10個のマスの中に左詰めで四つの駒が入っている。
一度にひとつの駒を1マスだけ右に動かす事にすると、Fig.1の右側のように右詰めにするには24手かかる。
では24手の動かし方、これは全部で何通りあるだろうか?
次のマスが埋まっている場合はその駒は動かせない (ひとつのマスにふたつの駒は入れられない) ものとする。
つまり、1手目は必ずAを動かして、2手目はAかBを動かすことになる。
Fig.1 24手
ソース
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
Console.WriteLine("正方向探索を行います。");
Dictionary<uint, int> SeiHoukouLeafDict = ExecDFS(true);
Console.WriteLine("正方向探索の葉ノード数={0}", SeiHoukouLeafDict.Count);
Console.WriteLine("逆方向探索を行います。");
Dictionary<uint, int> RevHoukouLeafDict = ExecDFS(false);
Console.WriteLine("逆方向探索の葉ノード数={0}", RevHoukouLeafDict.Count);
int ProdSum = 0;
foreach (var AnyPair in SeiHoukouLeafDict) {
ProdSum += AnyPair.Value * RevHoukouLeafDict[AnyPair.Key];
}
Console.WriteLine("解は{0}通り。経過時間={1}", ProdSum, sw.Elapsed);
}
struct JyoutaiDef
{
internal int Level;
internal char[] BanArr;
}
//深さ優先探索を行い(双方向探索なので方向を引数で指定する)
//葉ノードの状態ごとの件数を返す
static Dictionary<uint, int> ExecDFS(bool IsSeiHoukou)
{
var WillReturnDict = new Dictionary<uint, int>();
var stk = new Stack<JyoutaiDef>();
JyoutaiDef WillPush;
WillPush.Level = 0;
char[] wkArr = { 'D', 'C', 'B', 'A' };
if (IsSeiHoukou) { //正方向の探索
WillPush.BanArr = wkArr.Concat(Enumerable.Repeat(' ', 6)).ToArray();
}
else { //逆方向の探索
WillPush.BanArr = Enumerable.Repeat(' ', 6).Concat(wkArr).ToArray();
}
stk.Push(WillPush);
while (stk.Count > 0) {
JyoutaiDef Popped = stk.Pop();
if (Popped.Level == 12) {
uint BanUInt = BanToUInt(Popped.BanArr);
if (WillReturnDict.ContainsKey(BanUInt))
WillReturnDict[BanUInt]++;
else WillReturnDict[BanUInt] = 1;
continue;
}
WillPush.Level = Popped.Level + 1;
if (IsSeiHoukou) { //正方向の探索
for (int I = 1; I <= Popped.BanArr.GetUpperBound(0); I++) {
if (Popped.BanArr[I] == ' ' && Popped.BanArr[I - 1] != ' ') {
WillPush.BanArr = (char[])Popped.BanArr.Clone();
WillPush.BanArr[I] = Popped.BanArr[I - 1];
WillPush.BanArr[I - 1] = ' ';
stk.Push(WillPush);
}
}
}
else { //逆方向の探索
for (int I = 0; I <= Popped.BanArr.GetUpperBound(0) - 1; I++) {
if (Popped.BanArr[I] == ' ' && Popped.BanArr[I + 1] != ' ') {
WillPush.BanArr = (char[])Popped.BanArr.Clone();
WillPush.BanArr[I] = Popped.BanArr[I + 1];
WillPush.BanArr[I + 1] = ' ';
stk.Push(WillPush);
}
}
}
}
return WillReturnDict;
}
//盤面を符号なしInt型で表現
static uint BanToUInt(char[] pBanArr)
{
var sb = new System.Text.StringBuilder();
for (int I = 0; I <= pBanArr.GetUpperBound(0); I++) {
if (pBanArr[I] == ' ') sb.Append(0);
if (pBanArr[I] == 'A') sb.Append(1);
if (pBanArr[I] == 'B') sb.Append(2);
if (pBanArr[I] == 'C') sb.Append(3);
if (pBanArr[I] == 'D') sb.Append(4);
}
return Convert.ToUInt32(sb.ToString(), 8);
}
}
実行結果
正方向探索を行います。
正方向探索の葉ノード数=18
逆方向探索を行います。
逆方向探索の葉ノード数=18
解は140229804通り。経過時間=00:00:00.7464526
解説
開始と終了の状態が分かっているので双方向探索が使えますので、
双方向探索で、正方向で12手、逆方向で12手の探索を行ってから、
合流した葉ノードまでの組み合わせの数同士に対して、積の法則を使ってます。