問題
$N$ 個のクエリを順に処理する。1 A Bで直線 $y=Ax+B$ を追加、2 Xでこれまで追加した全直線について $x=X$ における最小値を出力する。保証: タイプ1の $A$ は出現順に厳密減少、タイプ2の $X$ は出現順に単調非減少。
入力形式
N
query_1
query_2
...
query_N
制約
$1 \le N \le 200000$
$-10^9 \le A,B,X \le 10^9$
タイプ1の $A$ は出現順に厳密減少
タイプ2の $X$ は出現順に単調非減少
入出力例
入力例1
7
1 2 0
1 1 1
1 -1 5
2 0
2 1
2 2
2 3
出力例1
0
2
3
2
直線 y=2x, y=x+1, y=-x+5 を追加。x=0→0, x=1→2(タイ), x=2→3(タイ), x=3→2。
概念図
ヒント(段階的開示)
ヒント1(方向性)
直線1本追加ごとに全直線で $x=X$ を計算すると $O(NQ)$ で間に合わない。Li Chao Treeは既習だが、今回は「傾きが追加順に単調減少」かつ「クエリの$x$も単調非減少」という強い制約がある特殊ケース。この二重の単調性を使うと、Li Chao Treeより軽い「単調CHT」が構成できる。
ヒント2(アプローチ)
直線を下側凸包として配列(実質スタック)で管理する。傾き減少順に追加すると不要になる直線が末尾から出るので pop しながら追加すると償却$O(1)$。クエリは $x$ が単調非減少なので、最小値を与える直線を指すポインタは前にしか進まず、これも償却$O(1)$。
ヒント3(誘導)
def bad(l1, l2, l3):
a1, b1 = l1
a2, b2 = l2
a3, b3 = l3
return (b3 - b1) * (a1 - a2) <= (b2 - b1) * (a1 - a3)
直線追加時は bad(hull[-2], hull[-1], new_line) が真の間 pop してから追加。クエリ時は「次の直線の方が現在より $x$ で以下」なら ptr を進める。
模範解答 (Python)
import sys
def solve():
data = sys.stdin.buffer.read().split()
idx = 0
n = int(data[idx]); idx += 1
hull = [] # (a, b) : y = a*x + b、傾き減少順に並ぶ下側凸包
ptr = 0
out = []
def bad(l1, l2, l3):
a1, b1 = l1
a2, b2 = l2
a3, b3 = l3
return (b3 - b1) * (a1 - a2) <= (b2 - b1) * (a1 - a3)
for _ in range(n):
t = int(data[idx]); idx += 1
if t == 1:
a = int(data[idx]); idx += 1
b = int(data[idx]); idx += 1
line = (a, b)
while len(hull) >= 2 and bad(hull[-2], hull[-1], line):
hull.pop()
hull.append(line)
if ptr > len(hull) - 1:
ptr = len(hull) - 1
else:
x = int(data[idx]); idx += 1
while ptr + 1 < len(hull) and hull[ptr + 1][0] * x + hull[ptr + 1][1] <= hull[ptr][0] * x + hull[ptr][1]:
ptr += 1
a, b = hull[ptr]
out.append(str(a * x + b))
print('\n'.join(out))
solve()
Step-by-Step 解説
1下側凸包の維持
傾き減少順に追加される直線を、必要な直線だけ配列に保持する。末尾2本と新直線で「真ん中が不要か」を判定し、不要なら pop してから再判定する(monotone chain と同じ発想)。
傾き減少順に追加される直線を、必要な直線だけ配列に保持する。末尾2本と新直線で「真ん中が不要か」を判定し、不要なら pop してから再判定する(monotone chain と同じ発想)。
2bad判定の意味
$l_1,l_3$の交点のy座標が$l_2$上のそれ以下なら$l_2$は不要。分母の符号を揃えた整数の外積比較で浮動小数点誤差を回避する。
$l_1,l_3$の交点のy座標が$l_2$上のそれ以下なら$l_2$は不要。分母の符号を揃えた整数の外積比較で浮動小数点誤差を回避する。
3クエリの単調ポインタ
xが単調非減少なので、次の直線の方が現在以下の間
xが単調非減少なので、次の直線の方が現在以下の間
ptr を進める。戻る必要がなく総移動量がhullサイズを超えないため償却$O(1)$。4追加とポインタの整合性
popで
popで
hullが短くなった場合、ptrが範囲外を指さないようptr = min(ptr, len(hull)-1)で補正する。5計算量
追加・クエリともに償却$O(1)$、全体で$O(N)$。
追加・クエリともに償却$O(1)$、全体で$O(N)$。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| bad判定を浮動小数点の交点計算で書く | float比較の誤差蓄積 | 整数のみの外積形式で判定する |
| xが単調でないのに単調ポインタ法を適用 | 前提条件の確認不足 | 単調性がない場合は二分探索版CHTやLi Chao Treeを使う |
直線追加時にptrが範囲外を指す | pop後の長さとptrの不整合 | 追加ごとにptr = min(ptr, len(hull)-1)で補正 |
| bad の等号条件を誤る | <=と<の混同 | 同じ最小値を与える直線が複数あってもよいなら<=で積極的に消す |
次のステップ
- 発展: xが単調でない場合は非単調CHT(二分探索)、直線が動的に増減するならLi Chao Treeを使う
- 次回予告: Kd-tree(k次元空間の最近傍探索・軸交互分割)