考え方
式変形(因数分解など)で何かを見出そうとしても上手く行かない.2変数の問題は,
- 2次元平面で考える
- 1変数を固定して考える
$0 \leq N \leq 10^{18}$だから,$0 \leq a,b \leq 10^{6}$である.よって,$a,b$の全探索はできない.
$f(a,b) = a^{3} + a^{2}b + ab^{2} + b^{3} = (\text{定数}) = X$は,2次元平面上の1つの曲線を与える.今回は,$X \geq N$をなるべく$N$に近くとりつつ,非負整数の格子点$(a, b)$を通る曲線を探したい.
$a$を固定できたとして,$b_{a} = \min \{b \mid f(a,b) \geq N \}$を求める方法を考える.
まず,$f(a,b)$は$b$の単調増加関数だから,二分探索($O(\log (10^{6}))$)が使える.$a$を全探索しても$O(10^{6} \log (10^{6}))$で間に合う.
あるいは,$b_{a}$が$a$に関する単調減少関数であることに気づくと,$a$を$+1$するたびに$b_{a}$は減るか変わらないかだから,二分探索を使わずに解ける($O(10^{6})$).
Editorial - AtCoder Beginner Contest 246
回答例
【二分探索】N = int(input()) ans = N ** 3 for a in range(10 ** 6 + 1): ok = 10 ** 6 ng = -1 while ok - ng > 1: mid = (ok + ng) // 2 x = a ** 3 + (a ** 2) * mid + a * (mid ** 2) + mid ** 3 if x >= N: ok = mid else: ng = mid x = a ** 3 + (a ** 2) * ok + a * (ok ** 2) + ok ** 3 ans = min(ans, x) print(ans)
【探索の工夫】
N = int(input()) def f(a, b): return a ** 3 + (a ** 2) * b + a * (b ** 2) + b ** 3 ans = N ** 3 b = 10 ** 6 for a in range(10 ** 6 + 1): while b > 0 and f(a, b) >= N: b -= 1 if f(a, b) < N: b += 1 ans = min(ans, f(a, b)) print(ans)
$a$を増やしたのに$b$が変わらなければ,答えとしてほしい最小値を更新することはない.つまり,$a$が増えれば,必ず$b$は1以上小さくなるから,以下でも良い.
N = int(input()) def f(a, b): return a ** 3 + (a ** 2) * b + a * (b ** 2) + b ** 3 ans = N ** 3 b = 10 ** 6 for a in range(10 ** 6 + 1): while b > 0 and f(a, b) >= N: ans = min(ans, f(a, b)) b -= 1 print(ans)
$f(a,b)$は$a, b$に対して対称な式だから,$a \leq b$の範囲だけを探索すればよい.「しゃくとり法」の形で実装できる.
N = int(input()) def f(a, b): return a ** 3 + (a ** 2) * b + a * (b ** 2) + b ** 3 ans = N ** 3 a = 0 b = 10 ** 6 while a <= b: if f(a, b) >= N: ans = min(ans, f(a, b)) b -= 1 else: a += 1 print(ans)