2006年3月に休刊となった月刊Cマガジン(ソフトバンククリエイティブ発行)に「Cマガ電脳クラブ」という連載コーナーがありました。そこで見た問題。

3435という数は、1桁ずつに分けて、それぞれの数を自分と同じ数でべき乗して足し合わせると、元の数である3435になる。
3435 = 3の3乗 + 4の4乗 + 3の3乗 + 5の5乗
このような数を“開き直り数”と呼ぶことにする。ちょっと考えれば1(=1の1乗)もこの種の数であることがわかる。
では1以上の整数で、この1と3435以外の開き直り数をすべて見つけていただきたい。
ただし、ここでは0の0乗は0とする。

CマガジンはおもにC言語を扱っていた情報誌です。しかしここではC#をつかって解くことにします。

考え方

まず問題文に「すべて見つけていただきたい」とあるので解は有限であることがわかります。ではどうやって解の候補を有限個に絞り込めばいいのでしょうか?

まず「1桁ずつに分けてそれぞれの数を自分と同じ数でべき乗して足し合わせる」という操作を「開き直り操作」と定義します(あくまでも個人的な定義)。

するとある自然数に開き直り操作をすると得られる数は以下のメソッドで得られます。

それからi桁の自然数の最大値は

Math.Pow(10,i) – 1

となり、最小値は

Math.Pow(10,i – 1)

となります。

またi桁の自然数に開き直り操作をして得られる数の最大値は

i * Math.Pow(9, 9)

なり、最小値は 1 となります。

そこで

i桁の自然数に開き直り操作をして得られる数の最大値 < i桁の自然数の最小値

であったり

i桁の自然数に開き直り操作をして得られる数の最小値 > i桁の自然数の最大値

である場合は、開き直り数は存在しないということになります。

i桁の自然数に開き直り操作をして得られる数の最小値 > i桁の自然数の最大値

とは

1 > Math.Pow(10,i) – 1

なので、これを満たすiは存在しません。そこで

i桁の自然数に開き直り操作をして得られる数の最小値 > i桁の自然数の最大値

が存在するかどうかを調べます。

i * Math.Pow(9, 9) < Math.Pow(10, i) - 1

を満たすiがあるならi桁以上の自然数には開き直り数が存在しないことになります。

実際に調べてみましょう。

これを実行するとループを抜けたときに i = 10 になっていることがわかります。10桁以上の数には開き直り数は存在しないことがわかります。

全部調べるのは効率が悪すぎ

上限がわかったのであとはループ文を使えば答えがわかるかもしれません。

しかしこれは処理に時間がかかりそうです。ためしに 1 ~ 999999(6桁)でやってみましたが、1分くらいかかりました。9桁の数までやろうとすると相当時間がかかるのではないでしょうか?

そこでもっと短い時間でやる方法を考えます。

上記のやりかたでは同じ数字の組み合わせでできる数を何度も検証していることになります。3桁に限定すると、123 / 132 / 213 / 231 / 312 / 321 に対して開き直り数操作をするとすべて同じ値(1+4+27=32)になります。

そこでまず「桁の組み合わせ」を考えて、これが開き直り数の条件を満たすかを考えたほうがよさそうです。

0~9の重複組み合わせを考える

10桁で0~9の10種類の数字で構成される組み合わせを考えます。同じ数字を同じ回数使うのであればそれで1つと考えます。

1234 と 1324は同じと見なします。また1231と1223は2を使っている回数が違うので別物として見なします。

重複組み合わせをC#で簡単にできないかと探したところ、こんなページが見つかりました。

C#:n個の要素からk個を選ぶ組合せを列挙する – Qiita

ここで紹介されているコードをパクらせて参考にさせていただきましょう。

ちょっとだけ手直ししています。引数を減らしたのと組み合わせでも重複はカウントしないといけないのでその部分を変更しています。

これを使えば、9桁の重複組み合わせを取得することができます。

では得られた重複組み合わせに対してどのような処理をすればよいのでしょうか?

まず開き直り操作をして得られる値を計算します。そして得られた重複組み合わせを並べ替えて開き直り操作をして得られる値に一致するものがあるかどうか調べます。

重複組み合わせを並べ替えて開き直り操作をして得られる値に一致するものがあるかどうかは、以下の方法でわかります。

両者を1桁ずつに分けて小さい順に並べ替える。
これで文字列をつくる
0は消去する
ふたつの文字列が一致するかどうか?

これを判定するのが自作メソッド bool IsHirakinaori(long value, int[] ints)です。

実行結果

1
3435
438579088

1以上の整数で1と3435以外の開き直り数は438579088だけでした。