https://atcoder.jp/contests/past202107-open/tasks/past202107_l
前提知識
解説
https://atcoder.jp/contests/past202107-open/submissions/24471772
解法を思いつくのも難しいが、ならし計算量(償却計算量)も意識する必要があり、そこも難しい。
セグメントツリーの知識が要求される。もし知らない場合はどこかで学習してくるか、AtCoder Libraryを活用するのもいいだろう。
何から考え始めるか
まず問題をパッと見て、単一要素更新と、区間最小値なのでセグメントツリーっぽさがある。
区間の中の最小値そのものではなく、最小値を持つ添え字を答えるというのが特徴となるが、
まずはセグメントツリーベースで考えていこう。
全て列挙するとあるが、1つ列挙することはできないだろうか。
これはセグメントツリーのノードを工夫することで実現ができる。
通常は要素のみを入れるが、{要素,添え字}というpairで入れることにする。
つまり{A[i],i}を入れるということになるのだが、これを入れてそのままmin関数で最小値を持ってきたとする。
こうすると、pairのmin比較では、firstが小さい方が採用され、firstが同じであればsecondの小さい方が採用される。
つまり、要素が最も小さく、かつ、その中でも添え字が最も小さいものが選ばれる。
こうすることで、セグメントツリーから得られる情報は要素が最も小さく、区間の中で最も左にある要素が得られることになる。
ここまで理解できていれば、実はこれ以降はそれほど難しくなくて、区間を小さくしながら要素を愚直に持ってくるだけでいい。
クエリ1
クエリ1は単純にセグメントツリーの更新処理を走らせればいい。
{Y, X}でX番目を更新する。
クエリ2
問題はこっち。
最初に区間全体でセグメントツリーから取ってきて、最小値をメモっておこう。
ここから区間の左端を狭めながら、最小値となる添え字を全列挙していく。
最初は[X,Y]の区間でセグメントツリーから持ってきて、{min, a}が得られたとする。
これは[X,Y]の区間の最左の要素となるので、次に見る区間はそれ以降でよいということになる。
よって、区間を[a+1,Y]のように更新して再度セグメントツリーから持ってくる。
同じ最小値のものが持ってこれたら再度答えとしてメモって、区間を狭くする。
これを繰り返していけば、要素が全列挙できることは分かるだろう。
間に合うのか?
とても素直な回答で、間に合うのか?という疑問が出てくるかもしれない。
今回は制約の「列挙すべき個数は2×105を超えない」というのが効いてくる。
この探索作業は必ず「列挙すべき個数+1」回の探索を行う(1回は失敗分)。
これをすべてのクエリについて足し合わせると、「列挙すべき個数の総和+Q」回となり、
多くても4×105回となるため、全体で考えると回数的には問題ないので間に合う。
このように計算回数にばらつきはあるのだけれど、全体で見るとある一定の回数に収まるというのを
ならし計算量、償却計算量で考えると表現する。
難しい考え方ではあるが、イメージをつかめば使いこなせるだろう。
int N, Q, A[201010]; SegTree<1<<18> st; //--------------------------------------------------------------------------------------------------- void _main() { cin >> N >> Q; rep(i, 0, N) cin >> A[i]; rep(i, 0, N) st.update(i, { A[i], i }); rep(_, 0, Q) { int T, X, Y; cin >> T >> X >> Y; if (T == 1) { X--; st.update(X, { Y, X }); } else { X--; int mi = st.get(X, Y).first; vector<int> ans; while (X < Y) { auto p = st.get(X, Y); if (p.first != mi) break; ans.push_back(p.second); X = p.second + 1; } int M = ans.size(); printf("%d", M); rep(i, 0, M) printf(" %d", ans[i] + 1); printf("\n"); } } }