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

Cマガ電脳クラブ(第040回) 小町算で小町数

問題

1から9までが1回ずつ登場する数を小町数、
1から9までを1個ずつ使った計算を小町算と娯楽数学家は呼んでいる。

今回は小町かけ算で小町数を作ってみた。たとえば、
    256384197 = 9×621×45873
がその例である。同じ小町数を別の小町算で表せる例もある。
    173549628 = 378×459126
              = 3×9×84×76521
    413972856 = 72×891×6453
              = 81×792×6453
              = 9×6453×7128
では、ある小町数を4通りの小町算で表せるか。あればそれは何通りあるか、すべてを見つけてほしい。
5通り以上で表せるかも調べてほしい。


ソース

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

class Program
{
    struct JyoutaiDef
    {
        internal int CurrP;
        internal List<int> ValList;
        internal int ProdVal;
    }

    static Dictionary<int, List<string>> ResultDict =
        new Dictionary<int, List<string>>();

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

        var KouhoValList = new List<int>();
        //1を使うと、積が9桁にならないので、ループは2から
        for (int I = 2; I <= 98765432; I++) {
            if (IsValidVals(new List<int>() { I }))
                KouhoValList.Add(I);
        }
        int[] KouhoValArr = KouhoValList.ToArray();
        Console.WriteLine("KouhoValArr.Length={0}", KouhoValArr.Length);

        var stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        for (int P = 0; P <= KouhoValArr.GetUpperBound(0); P++) {
            if (KouhoValArr[P] * KouhoValArr[P] > 987654321) break;
            WillPush.CurrP = P;
            WillPush.ValList = new List<int>() { KouhoValArr[P] };
            WillPush.ProdVal = KouhoValArr[P];
            stk.Push(WillPush);
        }

        int CombiCnt = 0;

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

            int KetasuuSum = Popped.ValList.Sum(X => DeriveKetasuu(X));
            if (KetasuuSum == 9) {
                if (DeriveKetasuu(Popped.ProdVal) < 9) continue;
                if (IsValidVals(new List<int>() { Popped.ProdVal }) == false) continue;

                var sb = new System.Text.StringBuilder();
                for (int I = 0; I <= Popped.ValList.Count - 1; I++) {
                    sb.Append(Popped.ValList[I]);
                    if (I < Popped.ValList.Count - 1)
                        sb.Append('*');
                }
                sb.AppendFormat(" = {0}", Popped.ProdVal);

                if (ResultDict.ContainsKey(Popped.ProdVal))
                    ResultDict[Popped.ProdVal].Add(sb.ToString());
                else ResultDict[Popped.ProdVal] = new List<string>() { sb.ToString() };

                if (++CombiCnt % 100 == 1)
                    Console.WriteLine("{0}。{1}個目の小町算と小町数。{2}",
                        sw.Elapsed, CombiCnt, sb.ToString());

                continue;
            }

            for (int P = Popped.CurrP + 1; P <= KouhoValArr.GetUpperBound(0); P++) {
                int KouhoKetasuu = DeriveKetasuu(KouhoValArr[P]);

                //桁数の和で枝切り
                if (KetasuuSum + KouhoKetasuu > 9) break;
                if (KetasuuSum + KouhoKetasuu < 9 && KetasuuSum + 2 * KouhoKetasuu > 9) continue;

                long wkProdLong = (long)(Popped.ProdVal) * KouhoValArr[P];

                if (wkProdLong > 987654321) break; //積の上限を超えたら枝切り
                int wkProdInt = (int)wkProdLong;
                if (wkProdInt % 10 == 0) continue; //1桁目が0なら枝切り

                WillPush.CurrP = P;
                WillPush.ValList = new List<int>(Popped.ValList) { KouhoValArr[P] };
                if (IsValidVals(WillPush.ValList) == false) continue;
                WillPush.ProdVal = wkProdInt;
                stk.Push(WillPush);
            }
        }

        //4通り未満の小町算でしか表せない小町数をRemove
        foreach (int AnyKey in ResultDict.Keys.ToArray())
            if (ResultDict[AnyKey].Count < 4) ResultDict.Remove(AnyKey);

        foreach (var AnyPair in ResultDict.OrderBy(X => X.Key)) {
            Console.WriteLine("{0}通りの小町算で表せる、小町数{1}を発見",
                AnyPair.Value.Count, AnyPair.Key);
            foreach (string AnyStr in AnyPair.Value.OrderBy(X => X))
                Console.WriteLine(AnyStr);
        }
    }

    //有効な数値リストかを判定
    static bool IsValidVals(List<int> pValList)
    {
        bool[] IsAppearedArr = new bool[10];

        foreach (int AnyInt in pValList) {
            int CopiedVal = AnyInt;
            do {
                int ModVal = CopiedVal % 10;

                //0を含んだらNG
                if (ModVal == 0) return false;

                //同じ数字を2個以上含んだらNG
                if (IsAppearedArr[ModVal]) return false;
                IsAppearedArr[ModVal] = true;
            } while ((CopiedVal /= 10) > 0);
        }
        return true;
    }

    //数値の桁数を返す
    static int DeriveKetasuu(int pVal)
    {
        if (pVal < 10) return 1;
        if (pVal < 100) return 2;
        if (pVal < 1000) return 3;
        if (pVal < 10000) return 4;
        if (pVal < 100000) return 5;
        if (pVal < 1000000) return 6;
        if (pVal < 10000000) return 7;
        if (pVal < 100000000) return 8;
        return 9;
    }
}


実行結果

省略
00:04:54.7255640。2601個目の小町算と小町数。3*71645289 = 214935867
00:04:54.7407977。2701個目の小町算と小町数。3*56427918 = 169283754
00:04:58.8258756。2801個目の小町算と小町数。3*762*85419 = 195267834
4通りの小町算で表せる、小町数127359648を発見
32*459*8671 = 127359648
6*9*754*3128 = 127359648
9*23*754*816 = 127359648
92*754*1836 = 127359648
4通りの小町算で表せる、小町数132759648を発見
4*56*729*813 = 132759648
9*24*756*813 = 132759648
9*54*273168 = 132759648
972*136584 = 132759648
4通りの小町算で表せる、小町数164759832を発見
4*9*86*53217 = 164759832
438*516*729 = 164759832
5913*27864 = 164759832
9*216*84753 = 164759832
4通りの小町算で表せる、小町数169857324を発見
3276*51849 = 169857324
36*4718259 = 169857324
4*91*567*823 = 169857324
6*7*54*91*823 = 169857324
4通りの小町算で表せる、小町数172349856を発見
3*672*85491 = 172349856
46*72*98*531 = 172349856
6*7*84*92*531 = 172349856
8*92*413*567 = 172349856
4通りの小町算で表せる、小町数419527836を発見
51*972*8463 = 419527836
6*93*751842 = 419527836
62*91*74358 = 419527836
63*918*7254 = 419527836


解説

小町数の配列の経路に対する、深さ優先探索の中で
・桁数の和で枝切りが2つ
・積の上限を超えたら枝切り
・積の1桁目が0なら枝切り
といった枝切りをBreak文とContinue文を使い分けて仕掛けてます。