Official

B - A^A Editorial by Nyaan


はじめに、方針の大枠として「 \(A\) を全探索する」という方針を取ることにします。つまり、

  • 適切な範囲について \(A\) を全探索して、各 \(A\) について \(A^A = B\) を満たすかを調べる。

というアルゴリズムを考えます。ここで問題になるのは、

  • 「適切な範囲」とはどのような範囲か?
  • \(A^A = B\) をどのように判定するのか?

という 2 点です。順に解説します。

まず、「適切な範囲」について説明します。 \(A^A\)\(A\) が大きくなればなるほど \(A^A\) の値も大きくなっていくような性質を持ちます。よって、\(B\) の範囲が \(1\) 以上 \(10^{18}\) 以下であることも踏まえると、次の事実が分かります。

  • \(x\)\(x^x = 1\) を満たす実数、\(y\)\(y^y = 10^{18}\) を満たす実数としたとき、\(A\)\(x \leq A \leq y\) を満たす範囲だけ調べればよい。

つまり、\(A\) は適切に上限・下限を定めてその範囲を調べればよいということがわかります。
上限・下限を計算します。まず、下限は\(1^1 = 1\) より \(1\) です。
\(A\) の上限を考えてみます。これは実験して確かめるのが楽で、

\[15^{15} = 437893890380859375 \simeq 4.38 \times 10^{17}\]

\[16^{16} = 18446744073709551616 \simeq 1.84 \times 10^{19} \]

であることから、\(y^y = 10^{18}\) を満たす \(y\)\(15\)\(16\) の間にあり、\(A\) の上限は \(15\) に設定すればよいことがわかります。
なお、\(A\) の上限を適切に決めずに探索すると、言語型によってはオーバーフローして判定が適切に行われなくなる問題が起こります。

次に、 \(A^A = B\) を判定する方法を説明します。
言語によっては pow 関数 (C++ の std::pow や Python の math.pow ) を使うのが分かりやすそうですが、実はこのような関数を使うと浮動小数点数型(小数の型のこと)に起因する誤差が発生します。
例えば \(15^{15}\)pow を使ったやり方と整数型を使ったやり方の 2 通りで計算したのが次のプログラムおよび実行結果です。

  • プログラム
#include <cmath>
#include <iostream>
using namespace std;

int main() {
  {
    long long x = pow(15, 15);
    cout << x << endl;
  }
  {
    long long x = 1;
    for (int i = 0; i < 15; i++) x *= 15;
    cout << x << endl;
  }
}
  • 実行結果
437893890380859392
437893890380859375

\(15^{15} = 437893890380859375\) なので、前者の方法では誤差が発生していることが分かります。
このような問題を避けるために、巨大な整数に関する計算は小数を避けて整数型で計算するようにしましょう。

以上の点を踏まえて実装することでこの問題を解くことが出来ます。C++ による実装例は次の通りです。

#include <iostream>
using namespace std;

int main() {
  long long B;
  cin >> B;
  for (int A = 1; A <= 15; A++) {
    long long x = 1;
    for (int i = 0; i < A; i++) x *= A;
    if (x == B) {
      cout << A << endl;
      exit(0);
    }
  }
  cout << "-1" << endl;
}

posted:
last update: