問題はこちら
問題概要
縦横マスのマス目の一番上のマスから1つを選んで右か下に1マスずつ移動する. ただし, 行目のマスから下に移動することはできない. のについて, 行目のいずれかのマスに移動するための最小移動回数を求めよ.思考の流れ
グリッド上で右か下に移動するやつと言ったらDPだけど制約がでかいからきつそう. DP書いてみて高速化してもいいけど今回はやめておく.右か下にしか動かないからあるマスからあるマスの移動回数は2マス間のマンハッタン距離になる. 下への移動回数は回なので, 1行目のいずれかのマスとそのマスから到達可能な行目のマスとの横方向の距離の最小値が答え.
下に進むことができないようなマスがあるので, そのときは右に進む. また, 下に進むことができるのにわざわざ右に行く必要はないことは容易に想像がつく. よって1行目から順に, 一行目のマスからスタートしたら行目にはどこにいるかをシミュレーションしていけばでは解ける.
高速化できる点として, 下に行けるマスは「今どこにいるか」という情報の更新が必要なかったり, 下に行けないマスは連続になっているかつすべて同じ位置になっていることを利用したりするものが考えられる.
必要な部分のみ更新したい. 結局こんな問題が解きたい(クエリに答えたい).
- 更新
- 行目で以上以下にいるものをに移動させる. が範囲外ならに移動するものとする.
- 最小値取得
- 行目で今いるマスとスタート位置のマスの横方向の距離の最小値を求める.
ここで, 更新の際に最小値となり得るのは, 今いるマスが以上以下のもののなかでスタート位置が最も右のものであることはすぐにわかる. さらに, スタート位置が番目で今いるマスがという数列は常に広義単調増加であるので, 結局, 今いるマスが以上以下のもののなかで最も右のものが答えとなる. これはsetで管理すれば解ける. 具体的には,
(今いる位置, スタート位置)をsetで, 今いる位置とスタート位置の距離をmultisetでもち, 今いる位置が以上以下のところの一番右(に近い)のものだけを残し他は消す. 削除回数は各要素高々1回なのでまにあう. 残した部分の今いる位置とスタート位置の距離を計算すればmultisetで最小値が求められる.
他にも...
遅延セグ木でを (等差数列)に変更するクエリがでできたら以下のプログラムのように簡単に解ける(update_linear_queryが等差数列での更新).
int main() { int h,w;cin >> h >> w; constexpr int INF = 1e9 + 6; LazySegmentTree<ll> seg(w,0); for (int i = 0; i < h; i++) { int a,b;cin >> a >> b; seg.add_query(0,w,1); if (a == 1) seg.update_linear_query(a-1,b,0,INF); else seg.update_linear_query(a-1,b,1,seg[a-2]-a+2); ll ans = seg.get_query(0,w); cout << (ans >= INF ? -1 : ans) << endl; } return 0; }
区間min,maxなどは区間更新と相性がよく, 区間sumと区間addは相性が良い.
伝播させるときに交差と初項を伝播させればよい. 区間min+区間add+区間updateを一緒に使う場合は伝播に工夫が必要になる.
区間min+区間等差数列add+区間等差数列updateは普通に書いても無理そう...(できる人教えてください)
提出プログラム
https://atcoder.jp/contests/abc177/submissions/16420137https://atcoder.jp/contests/abc177/submissions/16411569
感想
解けそうで解けなかったので丁寧に書いてみた. 本番はセグ木解法にハマってしまってずっと実装してた.一部だけ更新するようなDPはインラインにする!