数弱でも除算を理解する
切り捨て、切り上げ、真に大きい最小の整数、真に小さい最大の整数
JSC2021のA問題で2WAした
屈辱の2WAである。さすがにA問題で2WAするなど思ってもいなかった。
ケアレスミスで1WAはわかる。人間誰でもミスはするのだから。でも2WAはいけない。
この問題で求められたのは、a/b
より真に小さい最大の整数を求めることである。
整数除算はいつもこんがらがる
私は曲がりなりにも旧帝大理系院卒であるが、いわゆる数弱(数学知識や計算能力に乏しい人間)であり整数除算はいつもバグり散らかす。
AtCoderのビギナー向けコンテンツに「切り上げ除算は(a+b-1)//b
だ」的なことが書いてあったときも、なぜそうなるのかすぐには理解できず自分を呪った。
そして例にもれず今回も苦渋をなめるに至ったので、忘れないうちにブログにストックしておこうと思い立った。
当然ながらA問題を2WAした事実を全世界に向けて公開など本当はしたくないが、恥を捨て二度と同じ過ちを繰り返さぬよう深く刻み込むのだ。
切り捨て:a//b
— タナイ (@okinawa__noodle) April 18, 2021
切り上げ:(a+b-1)//b
a/bより真に小さい最大の整数:(a-1)//b
a/bより真に大きい最小の整数:(a+b)//b
昨日のA問題みたいなの数弱すぎてメチャクチャいつも悩むんだけど、これで合ってるか?
使いそうな4パターン
今回の問題に準拠してまずはa
とb
が正の整数とする。自分の中で整理がついたら実数範囲まで拡張して考える。
- 切り捨て:
a//b
- 切り上げ:
(a+b-1)//b
a/b
より真に大きい最小の整数:(a+b)//b
a/b
より真に小さい最大の整数:(a-1)//b
もちろん、混乱したら場合分けを使って書くのでもよいが、混乱しているので大抵はそうしてもなおバグり散らかすのが私だ。
忘れないように、どうしてそうなるのかも書いておく。いつもわからなくなるので。
切り捨て
これは言わずもがなa//b
。非負数であれば特に気にせずやってしまっていいはず。
非負数の場合は言語によって異なるかも。
切り上げ
まず、愚直に場合分けするケース。
a%b==0
のとき、つまり割り切れる場合は、a//b
である。a%b!=0
のとき、つまり割り切れないとき、a//b+1
である。
ここまでは、少し考えるとわかる。これを場合分けなく実装したい。
すべてをa//b+1
すなわち(a+b)//b
で処理すると、割り切れる場合にのみ求めたい値より1大きくなってしまう。
であれば、ほんの少しだけ値を小さくしてあげればよいのではないか、というのが発想となる。
たとえば、ごく小さい正の値x
を用いて、(a+b-x)//b
というような形とすれば1大きくなる問題は解決される。
しかし、注意すべき点として、割り切れない値である場合に、x
を引いてしまうことによって元のa//b+1
の示す整数値より1以上小さくなるような反作用は回避せねばならない。
評価式がa//b
以下になってはいけないということである。
不等式を書けば以下のようになる。
つまり、割り切れない場合の条件としてはであればよい。ただし、割り切れる場合は、そもそもa//b
でよかったのだから、でも成り立つ。
ここで、b
は正の整数でありである。よく考えると、であれば条件を満たすことがわかる。
- の場合、であり割り切れるかに関係なく条件を満たす
- の場合、分母が1となりこれは必ず割り切れるため、条件はで満足する
したがって、最終的な結論は、(a+b-1)//b
となる。
a/b
より真に小さい最大の整数
よく考えるとこれは切り上げから1を引いた数である。すなわち場合分けをするのであれば、
a%b==0
のとき、a//b-1
a%b!=0
のとき、a//b
となる。場合分けをしない場合は先ほどの式から1を引けばよく、(a-1)//b
となる。
a/b
より真に大きい最小の整数
これも、同様にして切り下げに1を足した数である。すなわち、(a+b)//b
である。
最後に
これを書いている途中にも何度もバグりかけた。本当に数弱をなんとかしてほしい。