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

Problem114 ブロックを離して一列に敷き詰める方法の数

問題

長さ 7 ユニットからなる 1 列上に, 最低 3 ユニットの長さを持つ赤ブロックが置かれている.
ただしどの赤ブロック同士も, 少なくとも 1 ユニットの黒い正方形が間にある
(赤ブロックは長さが異なってもよい). これを敷き詰める方法は, ちょうど 17 通りある.


50 ユニットの長さの 1 列を敷き詰める方法は何通りあるか.

注意: 上の例では起こりえないが, 通常はブロックの大きさが複数混ざっていてもよい.
例えば, 8 ユニットの長さの 1 列では, 赤(3), 黒(1), 赤(4) を使うことができる.


ソース

using System;
using System.Collections.Generic;

class Program
{
    //const int Units = 7;
    const int Units = 50;

    //事前に組み合わせ数を求めておく、残マス数
    const int DerivedRestCnt = Units / 2;

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

        for (int I = 1; I <= DerivedRestCnt; I++) {
            DeriveCaseCntFromBlack(I, true);
            DeriveCaseCntFromBlack(I, false);
        }

        var Stk = new Stack<string>();

        for (int I = 1; I <= Units; I++) Stk.Push(new string('B', I));
        for (int I = 3; I <= Units; I++) Stk.Push(new string('R', I));

        long AnsCnt = 0;

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

            if (Popped.Length == Units) {
                AnsCnt++;
                continue;
            }

            bool IsNextBlack = Popped.EndsWith("R");
            int RestUnits = Units - Popped.Length; //残マス数

            if (RestUnits <= DerivedRestCnt) {
                if (IsNextBlack) AnsCnt += CaseCntStartBlackDict[RestUnits];
                else AnsCnt += CaseCntStartRedDict[RestUnits];
                continue;
            }

            if (IsNextBlack) {
                for (int I = 1; I <= Units - Popped.Length; I++)
                    Stk.Push(Popped + new string('B', I));
            }
            else {
                for (int I = 3; I <= Units - Popped.Length; I++)
                    Stk.Push(Popped + new string('R', I));
            }
        }
        Console.WriteLine("経過時間={0},解の数={1}", sw.Elapsed, AnsCnt);
    }

    //残りNマスでの組み合わせ数を求める
    static Dictionary<int, long> CaseCntStartBlackDict = new Dictionary<int, long>();
    static Dictionary<int, long> CaseCntStartRedDict = new Dictionary<int, long>();
    static void DeriveCaseCntFromBlack(int pUnits, bool IsStartBlack)
    {
        var Stk = new Stack<string>();
        if (IsStartBlack) {
            for (int I = 1; I <= pUnits; I++) Stk.Push(new string('B', I));
        }
        else {
            for (int I = 3; I <= pUnits; I++) Stk.Push(new string('R', I));
        }

        long CombiCnt = 0;

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

            if (Popped.Length == pUnits) {
                CombiCnt++;
                continue;
            }
            if (Popped.EndsWith("R")) {
                for (int I = 1; I <= pUnits - Popped.Length; I++)
                    Stk.Push(Popped + new string('B', I));
            }
            else {
                for (int I = 3; I <= pUnits - Popped.Length; I++)
                    Stk.Push(Popped + new string('R', I));
            }
        }
        if (IsStartBlack) CaseCntStartBlackDict[pUnits] = CombiCnt;
        else CaseCntStartRedDict[pUnits] = CombiCnt;
    }
}


実行結果

経過時間=00:00:04.3644245,解の数=16475640049


解説

事前に組み合わせ数を求めておくことで、計算量を減らしてます。

深さ優先探索ではなく動的計画法で解くこともできます。