−目次
日立ヴァンタラプログラミングコンテスト2024(AtCoder Beginner Contest 368)E,G問題メモ
日立ヴァンタラプログラミングコンテスト2024(AtCoder Beginner Contest 368)
ヴァンターラ ヴァンターラ 愛の国 ヴァンターラ
E - Train Delay
問題文
- Atcoder 国には 1 から N の番号がついた N 個の街と、1 から M の番号がついた M 本の電車が走っています。
- 電車 i は街 Ai を時刻 Si に発車し、街 Bi に時刻 Ti に到着します。
- 今、電車 1 の発車時刻が X1 だけ遅れました。
- 「時刻表通りなら電車 1 からの乗り継ぎで乗車可能だった全ての電車」に、電車 1 が遅れた場合も乗車可能にするためには、各電車をそれぞれ最小で何分遅らせればよいか、求めてください。
- すなわち、以下の条件を満たす X2,X3,...,XN を求めてください。
- Xi≥0
- 1≤i,j≤M を満たす全ての組 (i,j) について、「Bi=Aj かつ Ti≤Sj」ならば「Ti+Xi≤Sj+Xj」
- 上記の条件を満たす中で、X2+…+XM が最小のもの
- なお、X2+…+XM が最小になるような X2,…,XM の定め方は一意であることが証明できます。
制約
- 2≤N≤2×105
- 2≤M≤2×105
- 1≤Ai,Bi≤N
- Ai≠Bi
- 0≤Si<Ti≤109
- 1≤X1≤109
解法
問題設定がややこしい。
解法も「こうかな?」と思いついたものがWAやTLEになりやすく、
グラフの複雑さから、上手くいかないケースも究明しづらい。
「X2+…+XM が最小」という条件があるが、
「ある電車 i を余分に遅らせることで、他の j が少ない遅延で発車できる」なんてことはないので、
各電車について、時刻表通りではそいつに乗り継ぎ可能だった電車が全て到着し次第、すぐに発車してよい。
(または、遅延無しで発車可能なら時刻表通りとしてよい)
ある電車 i の遅延時間 Xi を計算したいとき、タイミングと、必要な情報を考える。
- タイミング:
- 時刻表通りでは i に乗り継ぎ可能だった全ての電車 j の遅延時間の確定以降
- 情報:
- そのような電車の中での Tj+Xj の最大値
- その最大値を M として、Xi=max として計算可能
- ただし、乗り継ぎできない電車の T_j+X_j は最大値の計算に含めてはいけない
S_i 以降に駅 B_i に着く電車(T_j \gt S_i である j)がどれだけ遅延しようが、
それは「もともと乗り継ぎ可能でない」電車なので i の遅延時間には影響させなくてよい。
必要なのは、T_j \le S_i である電車のみの遅延時間である。
イベントソートで解ける。
(S_i,1,i),(T_i,0,i) を、全てまとめてソートする。
0,1は、出発/到着の区別と、同率なら到着の方が先に処理されるようにするためのもの。
ソートされたイベントを順に処理する中で、処理中の時刻を t として、以下の情報を管理する。
- \mathrm{Arrival[i]}= 駅 i に(時刻表通りなら)t までに到着する電車の中で、遅延を含めた到着が最も遅いものの時刻
- 出発イベント処理時:
- X_i = \max(0, \mathrm{Arrival[A_i]}-S_i) とする
- 到着イベント処理時:
- \mathrm{Arrival[B_i]} ← T_i+X_i とする(←はchmax)
これで、適切なタイミングを守って更新が実現できる。
出発イベントの時に到着点の \mathrm{Arrival[B_i]} まで更新しようとしてしまうと、上手くいかない。
G - Add and Multiply Queries
問題文
- 長さ N の正整数列 A, B が与えられます。
- 以下の形式で与えられる Q 個のクエリを、与えられた順番に処理してください。
- クエリは次の 3 種類のいずれかです。
- タイプ 1 :
1 i x
の形式で与えられる。A_i を x に置き換える。 - タイプ 2 :
2 i x
の形式で与えられる。B_i を x に置き換える。 - タイプ 3 :
3 l r
の形式で与えられる。以下の問題を解き、答えを出力する。- はじめ v = 0 とする。i = l, l + 1, \dots ,r の順に、v を v + A_i もしくは v \times B_i で置き換える操作を行う。最終的な v として実現できる最大値を求めよ。
- ただし、入力で与えられるタイプ 3 のクエリの答えは 10^{18} 以下であることが保証されています。
制約
- 1 \leq N \leq 10^5
- 1 \leq A_i \leq 10^9
- 1 \leq B_i \leq 10^9
- 1 \leq Q \leq 10^5
- タイプ 1, 2 のクエリにおいて、 1 \leq i \leq N
- タイプ 1, 2 のクエリにおいて、 1 \leq x \leq 10^9
- タイプ 3 のクエリにおいて、 1 \leq l \leq r \leq N
- タイプ 3 のクエリにおいて、出力すべき値は 10^{18} 以下である
解法
ありがちな解法で考えると、セグメント木に (A_i,B_i) なんかを乗せ、 集約値の区間最大値を取得!としたいところだが、 それまでの v の値によって最適な選択が異なってくる。
Ai = 10, Bi = 3 v=3 のとき v+10 = 13 > v*3 = 9 Aiの加算をした方がよい v=9 のとき v+10 = 19 < v*3 = 30 Biの乗算をした方がよい
よって、セグメント木として「区間の集約値を前計算しておく」ができず、この方法は使えない。
問題文最後の以下の条件を使用する。
- タイプ 3 のクエリの答えは 10^{18} 以下であることが保証されています。
出力すべき値が 10^{18} 以下ということは、
区間に含まれる B_i のうち 2 以上のものは60個以下ということになる。
2^{60} \ge 10^{18} で2以上の数を60回かけると超えてしまうし、A_i の加算によっても v は減らないので。
つまり、区間が長いクエリの時でも、区間内の B_i の大半は 1 が敷き詰められているということになる。
「1を乗算」は意味が無いので、B_i=1 の時には必ず「A_i を加算」を選んでよい。 そのような i が連続していれば、A_i の区間和としてセグメント木でまとめて求められる。
結局、v=0 から以下を繰り返せば良い。
- [l,r) の中で、最も左の B_i \ge 2 である位置 i を取得
- 存在しない場合、A[l:r) の総和を v に加算して終了
- [l,i) の範囲は、A_i を加算する方を選択する。A[l:i) の総和を v に加算
- i は素直に比較して更新する。v←\max(v+A_i,v \times B_i)
- l←i+1 として最初に戻る
A_i に対しては1点更新・区間和を求められるFenwickTreeやセグメント木などで管理。
B_i に対しては、B_i \ge 2 のindexのみを管理するSortedSetや、1点更新・区間maxのセグメント木などで管理し二分探索すればよい。
最大60回の繰り返しで、答えが求まる。計算量は O(N \log{N} \log{(答えの制約上限)})
なんか、感覚的に “クエリ” は “問い合わせ”、つまり「答えを知らないので教えて」という質問であることを想像するので、 こういう風な「実は問う側が答えを分かってることを前提とした解法」は、ちょっと発想の外から殴られたような衝撃を感じる。
まぁ、「計算途中で 10^{18} を超えたら“overflow”を出力して」という仕様だったら別に問う側が知ってなくても成り立つし、 そこから競技プログラミングの問題上、煩雑な部分を省略しただけとも捉えられるので、勝手な感覚ではあるんだけど。