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

Cマガ電脳クラブ(第114回) 循環する数列

問題

次のような漸化式で表される数列がある。
 a(n+1)=f(a(n))−a(n)

ここで、f(x)は、xを構成する数字を降順に(大きな数字から順に)並べ替える関数である。
つまり「整数a(n)を降順に並べ替えたものからa(n)を引いたものをa(n+1)とする」ということだ
(各項は0以上の整数であることとする)。
たとえば、a(0)=547とすると,a(1)=754-547=207、a(2)=720-207=513、・・・となる。

この例の場合、a(5)で0になってしまうが、次の例のように、循環する場合もある。
 3996, 5967, 3798, 6075, 1575, 5976, 3789, 6084, 2556
この2556の次は3996に戻る。
この循環は9個の相異なる数で構成されているが、
では、これよりも長い、つまり10個以上の数で構成される循環をひとつお答えいただきたい。


ソース

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

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

        //数字を入れ替えての引き算なので、a1は9の倍数であり、
        //同様にa2もa3も9の倍数となる。
        //a0に循環するのだから、a0が9の倍数であることは必要条件である。
        for (int I = 0; I < int.MaxValue; I += 9) {
            if (I % 900000 == 0)
                Console.WriteLine("{0}を調査中。経過時間={1}", I, sw.Elapsed);

            var KouList = new List<int>();
            KouList.Add(I);

            bool HasJyunkan = false;
            int SyokouKetasuu = DeriveKetasuu(I);
            while (true) {
                int NextKou = DeriveNextKou(KouList.Last());

                //桁数が増えることのない漸化式なので
                //桁数が減ったら循環しない。よって枝切りできる。
                if (SyokouKetasuu > DeriveKetasuu(NextKou))
                    break;

                if (KouList.Contains(NextKou)) {
                    HasJyunkan = (KouList[0] == NextKou);
                    break;
                }
                KouList.Add(NextKou);
            }
            if (HasJyunkan && KouList.Count >= 10) {
                Console.WriteLine("解を発見。経過時間={0}", sw.Elapsed);
                for (int A = 0; A <= KouList.Count - 1; A++) {
                    Console.WriteLine("A({0})={1}", A, KouList[A]);
                }
                break;
            }
        }
    }

    //漸化式を使って次の項を求める
    static int DeriveNextKou(int pCurrKou)
    {
        return DeriveSortedNum(pCurrKou) - pCurrKou;
    }

    //構成する数字を降順に並べ替えた数値を返す
    static int DeriveSortedNum(int pTargetNum)
    {
        var NumList = new List<int>();

        int CopiedVal = pTargetNum;
        do {
            int ModVal = CopiedVal % 10;
            NumList.Add(ModVal);
        } while ((CopiedVal /= 10) > 0);
        NumList.Sort((a, b) => b.CompareTo(a));

        int SortedNum = 0;
        foreach (int EachNum in NumList) {
            SortedNum *= 10;
            SortedNum += EachNum;
        }
        return SortedNum;
    }

    //桁数を求める
    static int DeriveKetasuu(int pTargetNum)
    {
        if (pTargetNum < 10) return 1;
        if (pTargetNum < 100) return 2;
        if (pTargetNum < 1000) return 3;
        if (pTargetNum < 10000) return 4;
        if (pTargetNum < 100000) return 5;
        if (pTargetNum < 1000000) return 6;
        if (pTargetNum < 10000000) return 7;
        if (pTargetNum < 100000000) return 8;
        return 9;
    }
}


実行結果

省略
10800000を調査中。経過時間=00:00:25.6233968
11700000を調査中。経過時間=00:00:28.7240459
12600000を調査中。経過時間=00:00:31.8483555
13500000を調査中。経過時間=00:00:35.0265435
解を発見。経過時間=00:00:37.1874889
A(0)=14100507
A(1)=61310493
A(2)=35122617
A(3)=41409594
A(4)=58134816
A(5)=30519495
A(6)=69034815
A(7)=29619495
A(8)=70345926
A(9)=27308394
A(10)=71434926
A(11)=26209395
A(12)=73443825


解説

初項は9の倍数であるという必要条件を使って、計算量を減らしてます。