読者です 読者をやめる 読者になる 読者になる

mayoko’s diary

プロコンとかいろいろ。

AtCoder Regular Contest 060 D - 桁和 / Digit Sum

解法

本番はよくわからない解法で通したのですが, そちらも紹介したいと思います。

ある数 n に対して b を決めると, 次のような規則性があることに気付きます。

n/2+1 から n までは f(n/2+1, n) から始まって 1 ずつ f(b, n) の値が減っていきます。
n/3+1 から n/2 までは f(n/3+1, n) から始まって 2 ずつ f(b, n) の値が減っていきます。

...

n/(i+1)+1 から n/i までは f(n/(i+1), n) から始まって i ずつ f(b, n) の値が減っていきます。

この規則性通り調べることを考えると,

  •  \sqrt{n} までは普通に調べる
  • それ以降は, 上記の規則性に従って考えると, n/i の i の候補は高々  \sqrt{n} 個しかないのでそれをすべて調べる

というようにやれば良いです。

想定解もやはり  \sqrt{n} で区切って場合分けをします。 \sqrt{n} までは普通に調べ, そのあとは再帰の深さが 2 にしかならないことを利用します。b >  \sqrt{n} の時, n = p * b + q とおけます。一方で, s = p + q です。ゆえに, n-s = p*(b-1) となり, b の候補は n-s の約数に絞られます。これらを列挙して, 実際に解になっているかを確かめれば良いです。

想定解法っぽいコード

ll calc(ll n, ll b) {
    if (n < b) return n;
    return calc(n/b, b) + n%b;
}

ll solve(ll n, ll s) {
    for (ll i = 2; i*i <= n; i++) {
        if (calc(n, i) == s) {
            return i;
        }
    }
    vector<ll> cand;
    for (ll i = 1; i*i <= n-s; i++) {
        if ((n-s)%i == 0) cand.push_back((n-s)/i+1);
    }
    sort(cand.begin(), cand.end());
    for (ll el : cand) {
        if (calc(n, el) == s) return el;
    }
    if (n == s) {
        return n+1;
    }
    return -1;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    ll n, s;
    cin >> n >> s;
    cout << solve(n, s) << endl;
    return 0;
}

本番通したコード

ll calc(ll N, ll div) {
    if (N < div) return N;
    return N%div + calc(N/div, div);
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    ll N, S;
    cin >> N >> S;
    //for (int i = 2; i <= N; i++) {
    //    cout << i << " " << calc(N, i) << endl;
    //}
    ll last = -1;
    for (ll i = 2; i*i <= N; i++) {
        last = i;
        if (calc(N, i) == S) {
            cout << i << endl;
            return 0;
        }
    }
    for (ll i = last; i > 1; i--) {
        ll start = N/i+1;
        ll last = N/(i-1);
        ll tmp = calc(N, start);
        if (tmp >= S && (tmp-S)%(i-1) == 0) {
            ll o = (tmp-S)/(i-1);
            if (start+o > last) continue;
            cout << start+o << endl;
            return 0;
        }
    }
    if (S == N) cout << N+1 << endl;
    else if (S == 1) cout << N << endl;
    else cout << -1 << endl;
    return 0;
}