初参戦したコンテストでC問題を時間内に解けなかったのでメモ。
Aising Programming Contest 2022(AtCoder Beginner Contest 255) - AtCoder
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.
問題
C - ±1 Operation 1
要は与えられた整数$X$と、最も近い等差数列$S$の値との差の絶対値を求めよ、という問題。
こうして考えると簡単そうですね。
えぇ、簡単なんですよ、しょうもないミスをしなければ…
try(WA)
考え方
$X$が$S$の初項($S_{(1)}$)より小さければ$abs(S_{(1)}-X)$を返す
$X$が$S$の末項($S_{(N)}$)より大きければ$abs(S_{(N)}-X)$を返す
それ以外なら$S$から最も近い値を探す(2分探索)
この場合、$S$は$X$より小さい範囲と大きい範囲に分割できる。
その境界を挟む2点のうち、どちらかが$X$に最も近い。
という感じで実装したのが以下
def main(X, A, D, N):
def check(i):
return A+D*i - X < 0
seq_max = A+D*N
if A > seq_max:
seq_min = seq_max
seq_max = A
else:
seq_min = A
if X > seq_max:
return abs(seq_max - X)
elif X < seq_min:
return abs(seq_min - X)
else:
neg = 0
pos = N
while abs(neg - pos) > 1:
mid = (neg + pos) // 2
if check(mid):
neg = mid
else:
pos = mid
return min(abs(A+D*neg - X) , abs(A+D*pos - X))
テストデータ・テスト関数定義
# 縮小表示
test_data = [
{
"in": [6, 2, 3, 3],
"out": 1
},{
"in": [0, 0, 0, 1],
"out": 0
},{
"in": [998244353, -10, -20, 30],
"out": 998244363
},{
"in": [-555555555555555555, -1000000000000000000, 1000000, 1000000000000],
"out": 444445
},
]
def test_all(f):
for i, data in enumerate(test_data):
exp = data["out"]
ans = f(*data["in"])
result = "AC" if exp == ans else "WA"
print(f"{i+1} {result}: expected: {exp}, output: {ans}")
test_all(main)
良さそうに見えますが、上のコードには2箇所 の間違いがあります。
結果
提出 #32414158 - エイシングプログラミングコンテスト2022(AtCoder Beginner Contest 255)
ACとWAが約半々という結果に…
ここで時間切れとなってしまいました。
デバッグ
聡明な読者諸賢ならお気づきかと思いますが、以下が修正点です。
8行目:数列の最大(小)値はA + D * (N - 1)
である
18~19行目:整数$X$より小さい範囲(neg)、大きい範囲(pos)の初期値は、$D < 0$の場合逆になる
どちらも悲しくなるくらい愚かなミスです😡👹
以後、同じことを繰り返さないよう、戒めとしてここに残しておきます。
という訳で修正したのがこちら
def main(X, A, D, N):
def is_positive(i):
"""数列のi番目がXより大きいか"""
return A+D*i - X > 0
seq_max, seq_min = (A+D*(N-1), A) if D > 0 else (A, A+D*(N-1))
if X >= seq_max:
return abs(seq_max - X)
elif X <= seq_min:
return abs(seq_min - X)
else:
neg, pos = (0, N-1) if D > 0 else (N-1, 0)
while abs(neg - pos) > 1:
mid = (neg + pos) // 2
if is_positive(mid):
pos = mid
else:
neg = mid
return min(abs(A+D*neg - X) , abs(A+D*pos - X))
test_all(main)
結果
無事ACとなりました
提出 #32423981 - エイシングプログラミングコンテスト2022(AtCoder Beginner Contest 255)
推敲
公式解説 でのポイントは以下でしょうか
最初に$D > 0$となるように正規化する
2文探索を使わなくても解ける
1点目によって、今回のミスの一つは防ぐことができます。
『場合分けを減らす』というアイディアは色んな場面で活用できそうです。
2点目は盲点というかなんというか、ついつい覚えた手法(今回は2文探索)を使いたくなってしまいがちですが、それを使わず簡単に解けるなら越したことはありません。
状況に応じた柔軟な考え方ができるようになりたいものです。
def main(X, A, D, N):
if D < 0:
A = A+D*(N-1)
D = -D
seq_max, seq_min = A+D*(N-1), A
if X >= seq_max:
return abs(seq_max - X)
elif X <= seq_min:
return abs(seq_min - X)
else:
side1 = (X - A) % D
return min(side1, D - side1)
test_all(main)
結果:提出 #32433205 - エイシングプログラミングコンテスト2022(AtCoder Beginner Contest 255)
まとめ
数列の項数と、インデックス…注意
自由欄