- 問題概要
数字列 を 進法表記と見なしたとき, 以下になるような値が何種類あるか求めよ.
ただし, は に含まれる数字より大きいものとする.
考えたこと
入力例 1
22 10
- 22 を 3 進数と見なすと,
- 22 を 4 進数と見なすと,
- 22 を 5 進数と見なすと,
- 22 を 6 進数と見なすと,
により, 最初の2つだけが条件を満たします.
このことから, が小さいときには条件を満たし, を大きくするにつれて を超えてしまう閾値を見つければよさそうです.
以下, の長さが であるとし, 桁目を ( )と表します. このとき, を 進法表記と見なすと,
と表すことができ, これは が二桁以上の場合には, について単調増大であり, したがっていつかは よりも大きくなります.
一方で, が一桁の場合には, 何進法表記であっても
です. いくら を大きくしても を超えないことがあり得ますが, 問題文で問われているのは 以下になるような値が何種類あるかであるため, そのものが 以下であれば が, そうでなければ が解答になります
解いていく
まずは, 入力を受け取ります. 数字列 は整数として入力されますが, 進数展開を行うことを見越してリストとして受け取りましょう.
X = [int(k) for k in list(input())] d = max(X) M = int(input())
また, 数字列 を 進数展開したときの値を p_adic(X, n)
により計算します.
def p_adic(X, p): curr = 0 for k in X: curr += k curr *= p curr //= p return curr
以下のようなコードだと, TLEしてしまいます
for ループが の例 1
ans = 0 for j in range(d+1, M+1): if p_adic(X,j) <= M: ans += 1 print(ans)
for ループが の例 2
ans = 0 for j in range(d+1, M+1): if p_adic(X,j) <= M: ans = j - d else: break print(ans)
今回の制約では のため, より高速なアルゴリズムを構築する必要があります.
二分探索
今回の問題では が小さいときには条件を満たし, を大きくするにつれて, ある閾値を超えると条件を満たさなくなるという単調性があります.
したがって, その閾値を探す問題に帰着することができます. その際には,
という性質を使って, 条件の範囲を1ステップごとに半分にしていくことができます. 効率よく探すことで, の計算量で以上の閾値を求めていきましょう.
一般の二分探索については qiita.com
特に Python の二分探索をうまく書く方法については www.forcia.com
がそれぞれ読みやすく, 参考になります. ポイントとしては,
- 左端が
True
, 右端がFalse
となるように初期値ok
,ng
を設定する - 中央値
(ok + ng)//2
での条件を判定する- 左端が
True
, 右端がFalse
となる状態を維持しながらok
,ng
を変更する
- 左端が
ok
とng
の差が1になったとき,ok
の値が「条件を満たす の上限」である
ことになります.
解いていく
以上を踏まえて,
- が一桁の場合がコーナーケースになっていることに注意する
- 「 を 進数展開した結果が $M$ 以下である」をis_ok(j)
と表記し, 二分探索で最大の $j$ を求める
- 答えとして を出力する
を行うことにより, ACすることができます
X = [int(k) for k in list(input())] d = max(X) M = int(input()) def p_adic(X, p): curr = 0 for k in X: curr += k curr *= p curr //= p return curr def is_ok(j): curr = p_adic(X, j) if curr <= M: return True else: return False if len(X) == 1: if X[0] <= M: print(1) else: print(0) import sys sys.exit() ok = d ng = M + 1 while ng - ok > 1: mid = (ok + ng) // 2 # 平均(小数切り捨て) if is_ok(mid): ok = mid else: ng = mid print(ok-d)