B - 展望台選び / Choosing an Observation Deck Editorial by admin
Claude 4.6 Opus (Thinking)概要
\(N\) 個の展望台があり、各展望台での「空の広さ」は左右の山による遮り度で決まります。山の高さの更新クエリと、指定範囲内で空の広さが最大の展望台を求めるクエリを処理する問題です。
考察
空の広さの定義を理解する
展望台 \(p\) における空の広さ \(V_p = \frac{1}{1 + L_p + R_p}\) を最大化したいです。分母が小さいほど \(V_p\) は大きくなるので、\(L_p + R_p\) を最小化する展望台を見つければよいという点が重要な気づきです。
\(L_p\) と \(R_p\) の意味
- \(L_p = \max_{l < p} \frac{H_l}{p - l}\):左側の各山について「距離あたりの高さ」の最大値
- \(R_p = \max_{r > p} \frac{H_r}{r - p}\):右側について同様
直感的には、近くて高い山ほど視界を大きく遮ります。
制約の確認
\(N \leq 200\), \(Q \leq 200\) と非常に小さいです。したがって、各タイプ2クエリに対して愚直に全ての候補展望台 \(p\) について \(L_p\) と \(R_p\) を計算しても十分間に合います。
比較の正確さ
\(\frac{H_l}{p-l}\) は有理数なので、浮動小数点数で比較すると誤差が生じる可能性があります。問題文でも「厳密な値(有理数)に基づいて比較」と明記されています。そこで、分数を 通分して整数同士の比較 に帰着させます。
例えば \(\frac{a}{b}\) と \(\frac{c}{d}\)(\(b, d > 0\))の大小比較は \(a \times d\) と \(c \times b\) の比較で行えます。
アルゴリズム
- タイプ1クエリ:\(H[x]\) を \(h\) に更新するだけ。
- タイプ2クエリ:範囲 \([a, b]\) の各展望台 \(p\) について以下を計算:
- \(L_p\):\(l = 1, 2, \ldots, p-1\) を走査し、\(\frac{H_l}{p-l}\) の最大値を分数(分子・分母のペア)として管理。
- \(R_p\):\(r = p+1, p+2, \ldots, N\) を走査し、同様に最大値を求める。
- \(L_p + R_p\) を分数の加算で計算(通分する)。
- これまでの最小値と比較し、より小さければ更新。同値なら番号が小さい方を採用。
分数の比較は全て整数の掛け算に帰着させ、誤差なく処理します。
計算量
- 時間計算量: \(O(Q \times N^2)\)
- タイプ2クエリ1回あたり、最大 \(N\) 個の展望台を調べ、各展望台で \(L_p, R_p\) の計算に \(O(N)\) かかるため \(O(N^2)\)。\(Q\) 回で \(O(Q \times N^2)\)。\(N, Q \leq 200\) なので最大約 \(200 \times 200^2 = 8 \times 10^6\) 程度で十分高速。
- 空間計算量: \(O(N)\)
実装のポイント
浮動小数点を使わない:\(\frac{H_l}{p-l}\) の比較を
H[l] * Lp_den > Lp_num * (p - l)のように整数の積で行うことで、誤差を完全に排除しています。\(L_p + R_p\) の比較も同様:\(\frac{a}{b} + \frac{c}{d} = \frac{ad + cb}{bd}\) と通分し、2つの和の大小も
sum_num * best_val_den < best_val_num * sum_denで整数比較しています。Python の整数は多倍長:\(H_i\) が最大 \(10^9\)、距離が最大 \(200\) 程度なので、積は最大でも \(10^{18}\) 程度に収まり、64ビット整数の範囲内ですが、Python では整数オーバーフローの心配が不要です。
Fractionを使わなかった理由:Fractionはオーバーヘッドが大きいため、手動で分子・分母を管理する方が高速です(コード中にインポートはありますが未使用)。ソースコード
import sys
from fractions import Fraction
def main():
input_data = sys.stdin.buffer.read().split()
idx = 0
N = int(input_data[idx]); idx += 1
Q = int(input_data[idx]); idx += 1
H = [0] * (N + 1)
for i in range(1, N + 1):
H[i] = int(input_data[idx]); idx += 1
results = []
for _ in range(Q):
t = int(input_data[idx]); idx += 1
if t == 1:
x = int(input_data[idx]); idx += 1
h = int(input_data[idx]); idx += 1
H[x] = h
else:
a = int(input_data[idx]); idx += 1
b = int(input_data[idx]); idx += 1
best_p = -1
# V_p = 1/(1 + L_p + R_p), maximize V_p means minimize (L_p + R_p)
# We use Fraction for exact comparison
best_val = None # This will store L_p + R_p (we want to minimize)
for p in range(a, b + 1):
# Compute L_p
if p == 1:
Lp_num = 0
Lp_den = 1
else:
Lp_num = 0
Lp_den = 1
for l in range(1, p):
# H[l] / (p - l) vs Lp_num / Lp_den
hn = H[l]
hd = p - l
# Compare hn/hd vs Lp_num/Lp_den
if hn * Lp_den > Lp_num * hd:
Lp_num = hn
Lp_den = hd
# Compute R_p
if p == N:
Rp_num = 0
Rp_den = 1
else:
Rp_num = 0
Rp_den = 1
for r in range(p + 1, N + 1):
hn = H[r]
hd = r - p
if hn * Rp_den > Rp_num * hd:
Rp_num = hn
Rp_den = hd
# sum = Lp_num/Lp_den + Rp_num/Rp_den
# = (Lp_num * Rp_den + Rp_num * Lp_den) / (Lp_den * Rp_den)
sum_num = Lp_num * Rp_den + Rp_num * Lp_den
sum_den = Lp_den * Rp_den
if best_p == -1:
best_p = p
best_val_num = sum_num
best_val_den = sum_den
else:
# Compare sum_num/sum_den < best_val_num/best_val_den
# i.e. sum_num * best_val_den < best_val_num * sum_den
if sum_num * best_val_den < best_val_num * sum_den:
best_p = p
best_val_num = sum_num
best_val_den = sum_den
results.append(best_p)
sys.stdout.write('\n'.join(map(str, results)) + '\n')
main()
この解説は claude4.6opus-thinking によって生成されました。
posted:
last update: