けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AtCoder ABC 044 D - 桁和 (ARC 060 D) (1D, 黄色, 500 点)

 \sqrt{N} で場合分けする系!

問題概要

正の整数  N, S が与えられる。次の条件を満たす最小の整数  b b \ge 2)を求めよ。(存在しない場合は -1。)

【条件】
 N b 進法表記したときの桁の和が  S である

制約

  •  1 \le N, S \le 10^{11}

考えたこと

 b が小さい範囲は愚直に調べればよさそうだ。 b がある程度大きくなると、 N b 進法で表したときの桁数が小さくなる。具体的には、 b \gt \sqrt{N} のときには、 N b 進法で表すと 2 桁以内になる。

よって、次のような方針が立つ。(1 桁の場合を見落としやすいので注意。)


  •  b 進法で表したときの桁数が 3 桁以上: b \le \sqrt{N} の範囲を調べればよい
  •  b 進法で表したときの桁数が 2 桁:後述
  •  b 進法で表したときの桁数が 1 桁: N = S のときに限りあり得て、 b = N+1 が最小である

2 桁の場合

 b 進法で  xy と表せるとすると、次のようになる。

  •  x + y = S
  •  bx + y = N

これらを満たす整数  x, y 1 \le x \lt b, 0 \le y \lt b)が存在するような  b を求めれば良い。

ここでは、 x = 1, 2, \dots, \lfloor \sqrt{N} \rfloor を固定して調べることにした。 x を固定すると  y = S - x と求められるので、 b = \frac{N - y}{x} と求められるはずである( N - y x の倍数でなければならない)。

まとめ

上記の場合分けによって、 O(\sqrt{N} \log N) の計算量で答えが求められる。

#include <bits/stdc++.h>
using namespace std;

int main() {
    long long N, S, res = -1;
    cin >> N >> S;

    // b <= √N の場合を求める
    for (long long b = 2; b * b <= N; b++) {
        long long N2 = N, sum = 0;
        while (N2) {
            sum += N2 % b;
            N2 /= b;
        }
        if (sum == S) {
            cout << b << endl;
            return 0;
        }
    }

    // b > √N のときは b 進法で 2 桁以内なので、十の位の値で場合分け
    for (long long x = 1; x * x <= N && x <= S; x++) {
        long long y = S - x;
        if ((N - y) < 0 || (N - y) % x != 0) continue;
        long long b = (N - y) / x;
        if (b <= x || b <= y || b <= 1) continue;

        if (res == -1) res = b;
        else res = min(res, b);
    }

    // 1 桁の場合
    if (N == S && res == -1) res = N + 1;
    cout << res << endl;
}