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

Cマガ電脳クラブ(第085回) 省メモリものさし

問題

Fig.1のような、変な目盛の入ったものさしがある(数値は左端からの長さを表す) 。
実はこのものさし、目盛が4か所(両端を含む) にしかないのに、1〜6センチのすべての長さを測ることができる。
 1=1-0、2=6-4、3=4-1、4=4-0、5=6-1、6=6-0

このように、「1センチから全体の長さまで1センチ単位ですべての長さを測ることができるものさし」は、
目盛が4か所の場合この6センチが最長である。同様にして目盛が5か所の場合には、9センチが最長となる(Fig.2) 。
このとき、3センチに2通りの測り方があるが、
それぞれの長さが少なくとも1通りで測ることができればよいので、OKとする。

さて、ここで問題。目盛を9か所に入れるとしたとき、上記条件を満たすものさしは最長何センチだろうか

Fig.1 目盛が4か所のものさし


Fig.2 目盛が5か所のものさし


ソース

using System;
using System.Collections.Generic;

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

        DeriveMaxCenti(4);
        DeriveMaxCenti(5);
        DeriveMaxCenti(9);

        Console.WriteLine("経過時間={0}", sw.Elapsed);
    }

    //目盛数を引数として、何センチまで測れるかを調べる
    static void DeriveMaxCenti(int pMemoriCnt)
    {
        Console.WriteLine(new string('■', 30));
        Console.WriteLine("目盛数={0}で何センチまで測れるかを調べます", pMemoriCnt);

        //pMemoriCnt C 2 が可能な組み合わせなので、最大でもその長さまでしか測れない
        int LimitI = pMemoriCnt * (pMemoriCnt - 1) / 2;

        //右端の目盛でループ
        for (int I = pMemoriCnt - 1; I <= LimitI; I++) {
            IEnumerable<int[]> KouhoArrEnum = DeriveKouhoArrList(pMemoriCnt, I);
            Console.WriteLine("右端の目盛={0}で解を調査中", I);

            foreach (int[] EachKouhoArr in KouhoArrEnum) {
                if (IsValidKouhoArr(EachKouhoArr, I)) {
                    Console.Write("目盛");
                    Array.ForEach(EachKouhoArr, X => Console.Write("{0},", X));
                    Console.WriteLine("で1から{0}まで測れます。", I);
                    break;
                }
            }
        }
    }

    struct JyoutaiDef
    {
        internal int MemoriCnt;
        internal int CurrMemori;
        internal List<int> KouhoList;
    }

    //目盛数と、右端の目盛を引数とし、他の目盛の候補のListを返す
    static IEnumerable<int[]> DeriveKouhoArrList(int pMemoriCnt, int pMigihajiMemori)
    {
        var WillReturn = new List<int[]>();

        var stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        WillPush.MemoriCnt = 1;
        WillPush.CurrMemori = 0;
        WillPush.KouhoList = new List<int>() { 0 };
        stk.Push(WillPush);

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

            //クリア判定
            if (Popped.MemoriCnt + 1 == pMemoriCnt) {
                Popped.KouhoList.Add(pMigihajiMemori);
                yield return Popped.KouhoList.ToArray();
                continue;
            }

            for (int I = Popped.CurrMemori + 1; I <= pMigihajiMemori - 1; I++) {
                WillPush.MemoriCnt = Popped.MemoriCnt + 1;
                WillPush.CurrMemori = I;
                WillPush.KouhoList = new List<int>(Popped.KouhoList) { I };
                stk.Push(WillPush);
            }
        }
    }

    //1センチから全体の長さまで測れるかを判定
    static bool IsValidKouhoArr(int[] pKouhoArr, int pMigihajiMemori)
    {
        bool[] CanKeisokuArr = new bool[pMigihajiMemori + 1];
        for (int I = 0; I <= pKouhoArr.GetUpperBound(0) - 1; I++) {
            for (int J = I + 1; J <= pKouhoArr.GetUpperBound(0); J++) {
                int wkInd = pKouhoArr[J] - pKouhoArr[I];
                CanKeisokuArr[wkInd] = true;
            }
        }
        return Array.FindIndex(CanKeisokuArr, 1, X => X == false) < 0;
    }
}


実行結果

省略
右端の目盛=28で解を調査中
目盛0,2,4,6,7,18,19,27,28,で1から28まで測れます。
右端の目盛=29で解を調査中
目盛0,2,5,8,11,15,27,28,29,で1から29まで測れます。
右端の目盛=30で解を調査中
右端の目盛=31で解を調査中
右端の目盛=32で解を調査中
右端の目盛=33で解を調査中
右端の目盛=34で解を調査中
右端の目盛=35で解を調査中
右端の目盛=36で解を調査中
経過時間=00:01:32.6602386


解説

目盛が9箇所なので、1から最大でも9C2までの長さを測ることができます。
よって、9*8/2=36をループの上限としてます。