Atcoder Beginner Contest 192_D "Base n"
水色の問題.本番だとしんどそう.
問題の説明
数字のまでで成される文字列と整数が与えられる.に含まれる最大の数字がであるとき,ある整数進法の数としてをみる.このようにして進表記された数としてみたの値のうち以下となるものは何種類あるか.
制約
- の長さは以上以下.
考えた事
まずを進表記された数として見た時の値を考えよう.この値をとし,のsizeをとおくと,
となる.これが以下であればいいのだから,
を満たすようなについて考察すると答えに辿り着けそうだ.ここでの性質に注目すると,これは次函数であるからが非負整数である時に狭義単調増加する性質をもつ.つまりがある一定値までの時はを満たすが,もしそれを超えたらその先では絶対にを満たさないのだ.
このようなある一定ラインを超える(or 下回る)時に状態がパッキリ割れるような問題で,その一定ラインの検出を高速で行えるアルゴリズムがあった.そう,二分探索である.二分探索を行えばを満たすギリギリのを見つけ出せる.そうすれば解としてを出力すれば答えになりそうである.なぜなら今回の問題はがに対して「狭義」単調増加性を有しており,の変化に合わせて,つまりの値も変わるからである.
ここまでが考察の大筋である.さて,この考察は正しいだろうか.
恐らく試験の答案でこの議論をした場合,それなりに点を引かれてしまうだろう.どこがいけないだろうか.
答えは狭義単調増加性の部分だ.実は成り立たない場合がある.それはいつか.
の時だ.このときは定数函数となり,によらず取る値の種類は高々1つとなる.これを別立てて計算しておく必要がある. 具体的には,
if (X.size() == 1) { if (M - stoll(X) >= 0) { cout << 1 << endl; } else { cout << 0 << endl; } }
とするといい.との大小関係で答えが分岐する.
やや長くなってしまったがここまでで問題の考察はできた.つづいて実装である.これがやや難しい.
は近い値であるとき非常に大きな値となり,C++ではoverflowしてしまいそのままでは大小比較ができない.どうすればいいか.
こういった場合は「をで回割った値が以上ならでoverflowしていないから安心してを行う.」といった所作を繰り返すといい.
つまりいきなり掛けるのではなく,まず割って確認すればいいのだ.このテクニックはちょくちょく出てくる.自分のコードでは念のため,で割っているため,であるときの例外処理を書いていてやや煩雑となっている.
これに気を払えばあとはよくある二分探索の実装に載せるといい.とはいえ二分探索は往々にしてバグりやすい.これを防ぐために,
ll ok = 0; ll ng = 4 * INF; while (abs(ok - ng) > 1) { ~~~ }
のように絶対大丈夫なラインと絶対ダメなラインとで半開区間を考えるといい.この場合はのような半開区間を想定している.
また,while
内を絶対値で取っている理由もある.もし,のような半開区間を考えたい時は,
ll ok = 1000; ll ng = -1; while (abs(ok - ng) > 1) { ~~~ }
のように数値を変えるだけで式を変えずに操作を行える.これがいわゆる「めぐる式二分探索」1である. なにはともあれ,ここまでくれば後はコードを載せればわかるだろう.ここまでお読みいただきありがとうございました.
プログラム
#include <bits/stdc++.h> using namespace std; using ll = long long; #define rep(i, n) for (ll i = (0); i < (ll)(n); i++) int main() { string X; cin >> X; ll M; cin >> M; if (X.size() == 1) { if (M - stoll(X) >= 0) { cout << 1 << endl; } else { cout << 0 << endl; } } else { ll num = 0; rep(i, X.size()) { CHMAX(num, (ll)(X[i] - '0')); } ll ok = 0; ll ng = 4 * INF; while (abs(ok - ng) > 1) { ll mid = ok + (ng - ok) / 2; ll res1 = M; bool flag = true; rep(i, X.size()) { ll res2 = res1; if ((X[i] - '0') == 0) { continue; } rep(j, X.size() - i - 1) { res2 /= mid; } res2 /= (X[i] - '0'); if (res2) { ll res3 = (X[i] - '0'); rep(j, X.size() - i - 1) { res3 *= mid; } res1 -= res3; } else { flag = false; break; } } if (flag) { ok = mid; } else { ng = mid; } } cout << max(ok - num, (ll)0) << endl; } }