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) の値が減っていきます。
この規則性通り調べることを考えると,
- までは普通に調べる
- それ以降は, 上記の規則性に従って考えると, n/i の i の候補は高々 個しかないのでそれをすべて調べる
というようにやれば良いです。
想定解もやはり で区切って場合分けをします。 までは普通に調べ, そのあとは再帰の深さが 2 にしかならないことを利用します。b > の時, 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; }