$Q\ \le\ 2\times 10^5$ で解けるの凄い! ## 問題概要 https://atcoder.jp/contests/abc237/tasks/abc237_g 長さ$N$の順列$P$を与えます。以下の$Q$個のクエリを処理します。 クエリ`t l r` - $t = 1$ならば、$P_{l}, P_{l + 1}, \dots, P_{r - 1}, P_{r}$を昇順にソートする - $t = 2$ならば、$P_{l}, P_{l + 1}, \dots, P_{r - 1}, P_{r}$を降順にソートする すべてのクエリを処理した後、$P_i = X$を満たす$i$を報告してください #### 制約 $1\ \le\ N, Q\ \le\ 2\times 10^5$ ## 情報を削る・観察 普通にクエリを処理するのは難しいと感じました。[^1] 最終的に$P_i = X$を満たす$i$が求まれば良いことを念頭に、クエリにおいてそのような$i$がどう移動するかを観察します。 - 区間に該当する$i$が含まれていない場合 - 移動しない - 区間に$i$が含まれている時 - 昇順ソートの場合、その区間に含まれる$X$よりも小さい値より後ろに移動する。また、その区間に含まれる$X$より大きい値より前に移動する - 降順ソートの場合、反対になる(大きい値の後ろ、小さい値の前に移動する) よって、各値が$X$より小さい/等しい/大きいかのみが重要であることがわかります。 ここから、順列では無く、以下の数列$P'$上でクエリを処理します。 $$P'_i = \begin{cases} -1 & \text{if}\ P_i < X \\ 0 & \text{if}\ P_i = X \\ 1 & \text{else} \end{cases}$$ ## P'上でのクエリ処理 観察で見た通り、クエリ処理では区間に含まれる$X$より小さい数と区間に含まれる$X$より大きい数が重要でした。$P'$上ではこれを「区間に含まれる$-1$の数」と「区間に含まれる$1$の数」と言い換えることができます - このような数を求めるのは累積和等でできますね。実際はクエリ処理で要素の移動が発生するので、今回はセグメント木等の値の変更に強いデータ構造を利用することになります。 クエリの区間に含まれる$-1$の数を$a$、$0$の数を$b$、$1$の数を$c$とします。[^2] クエリが昇順ソートだった場合、区間$P_l, P_{l + 1}, \dots, P_{r - 1}, P_{r}$は$P_l, P_{l + 1}, \dots, P_{l + a}$は$-1$、$P_{l + a + 1}, \dots, P_{l + a + b}$は$0$、$P_{l + a + b}, \dots, P_{l + a + b + c = r}$は$1$になります。このような数列の変更は、Range Update Queryを$3$回行えば実現することが可能です。 降順ソートだった場合も同様に考えることができます。 以上により、例えば遅延セグメント木を利用する事で$P'$上のクエリ処理が実現することができました。 ## 提出コード 自分の遅延セグ木、RUQライブラリがかなりお粗末だった。 ```cpp= using tup = tuple<i32, i32, i32>; tup operator+(const tup& a, const tuple<i32, i32, i32>& b) { auto [x, y, z] = a; auto [p, q, r] = b; return tup{ x + p, y + q, z + r }; } tup operator*(i32 k, const tup& a) { auto [x, y, z] = a; return tup{ k * x, k * y, k * z }; } using vM = zawa::rangeAddMonoid<tuple<i32, i32, i32>>; struct oM { using valueType = tuple<i32, i32, i32>; static constexpr valueType identity{ -1, -1, -1 }; static valueType operation([[maybe_unused]] const valueType& a, const valueType& b) { return b; } }; struct action { using valueMonoid = vM; using operatorMonoid = oM; static valueMonoid::valueType mapping(const valueMonoid::valueType& a, const tuple<i32, i32, i32>& b) { return valueMonoid::valueType{ a.size * b, a.size }; } }; void main_() { i32 N, Q, X; in(N, Q, X); vector<vM::valueType> init(N); tuple<i32, i32, i32> pls = { 0, 0, 1 }, mns = { 1, 0, 0 }, eq = { 0, 1, 0 }; for (i32 i = 0 ; i < N ; i++) { i32 p; in(p); init[i].size = 1; if (p < X) init[i].value = mns; if (p == X) init[i].value = eq; if (p > X) init[i].value = pls; } zawa::lazySegmentTree<action> S(init); times(Q) { i32 c, l, r; in(c, l, r); l--; if (c == 1) { auto [m, e, p] = S.prod(l, r).value; assert(l + m + e + p == r); S.update(l, l + m, mns); S.update(l + m, l + m + e, eq); S.update(l + m + e, l + m + e + p, pls); } else if (c == 2) { auto [m, e, p] = S.prod(l, r).value; assert(l + m + e + p == r); S.update(l, l + p, pls); S.update(l + p, l + p + e, eq); S.update(l + p + e, l + p + e + m, mns); } else { assert(!"input failed"); } } i32 ans = -1; for (i32 i{} ; i <= N ; i++) if (get<1>(S.prod(0, i).value) == 1) { ans = i; break; } out(ans); } ``` [^1]: Library Checkerに区間ソートをする問題がありますが、自分はよくわかってないです。[問題](https://judge.yosupo.jp/problem/point_set_range_sort_range_composite) [^2]: $b$は$0$か$1$のどちらかとなります