AtCoder NoviStepsを埋めてみる(33) 最小費用流問題 負のコストが設定できる自作クラスの続きです。今回はセグメント木(segment tree)です。

セグメント木は「1 点更新」と「区間取得」を O(log N) の計算量で実行できるデータ構造です。区間最大値、区間最大値、区間和などを求める処理を高速でおこなうことができます。ac-library-csharp というライブラリには Segtree<T> クラスがあり、これを使えば簡単にセグメント木を構築することができます。今回はこれを使い倒すことにします。

参考: ac-library-csharpを使ってみる(セグメント木編)

A58 – RMQ (Range Maximum Queries)

A58 – RMQ (Range Maximum Queries)

問題の概要

長さ N の数列 A がある。最初はすべての要素が 0 である。
以下の 2 種類のクエリを処理せよ。

クエリ 1:A[pos] の値を x に更新する。
クエリ 2:A[l], …, A[r -1] の最大値を答える。

1点更新、区間最大値取得ができる RangeMaxQuery クラスを定義しておきます。あとはこれを使えばクエリを処理することができます。

A59 – RSQ (Range Sum Queries)

A59 – RSQ (Range Sum Queries)

問題の概要

長さ N の数列 A がある。最初はすべての要素が 0 である。
以下の 2 種類のクエリを処理せよ。

クエリ 1:A[pos] の値を x に更新する。
クエリ 2:A[l], …, A[r -1] の合計値を答える。

今度は区間最大値ではなく区間和を求める問題です。RangeMaxQuery クラスを少し変更するだけで対応できます。本質的には OP.Operate(long x, long y) を変えただけです。

F – Range Xor Query

F – Range Xor Query

問題の概要

長さ N の整数列 A が与えられる。
Q 個のクエリを処理せよ。

クエリ 1: A[X[i]] を A[X[i]] XOR Y[i] で更新する。
クエリ 2: A[X[i]], A[X[i] + 1], …, A[Y[i]] を出力する。

XOR もセグメント木で処理することができます。

セグメント木で扱える処理とはどのようなものなのでしょうか? 結論からいうとモノイド(monoid)であれば扱うことが可能です。モノイドとは以下のようなものです。

結合律
S の任意の元 a, b, c に対して、(a・b)・c = a・(b・c)

単位元の存在
S の元 e が存在して、S の任意の元 a に対して e・a = a・e = a

例えば足し算であれば (a + b) + c = a + (b + c) なので結合法則が成り立ちます。また 0 + a = a + 0 = a なので 0 が単位元となります。掛け算も結合法則が成り立ち、1 が単位元となります。最大値や最小値もやはり結合法則が成り立ち、-∞ や ∞ が単位元となります。XOR も同様に単位元が 0 であるモノイドです。

すなわち結合律と単位元が存在するならセグメント木を使った処理が可能です。

OP 構造体の定義を少し変えれば対応できます。

E – Set Meal

E – Set Meal

問題の概要

長さ N の整数列 A と 長さ M の整数列 B が与えられる。
さらにふたつの整数のペア(C[i], D[i]) L 組 が与えられる。

A[i] + B[j] の最大値を求めよ。
ただし (i, j) = (C[k], D[k]) となるものは選んではならない。

この問題は優先度付きキュー(priority queue)を使えば解くことができます。大きい順に A[i] + B[j] を求め、(i, j) = (C[k], D[k]) でないものが見つかればそれが解となります。

参照:優先度付きキュー(priority queue)を使って計算結果がK番目に大きくなる組を求める

これとは別にセグメント木を使う解法もあります。

どのようにするかというと・・・

① 各 i に対して A[i] と合わせられない B の添字 の集合を取得しておく。
② セグ木 seg を B で初期化する。
③ A[i] と合わせられない B の添字 j を 0 で更新
④ seg の最大値を取得
⑤ 0 に変更した要素を元に戻し、i + 1 以降も同様に ③ ④ の処理をする。

F – Rook Score

F – Rook Score

問題の概要

縦 10^9 マス、横 10^9 マスのマス目がある。
(R[i], C[i]) には正整数 X[i] が、他の 10^18 – N 個のマスには 0 が書かれている。

あるマス (r, c) を選んだとき、これと行または列が同じ 2 × 10^9 – 1 個のマスに書かれた整数の総和を S と定義する。
S の最大値を求めよ。

前問の応用問題です。縦 10^9 マス、横 10^9 マスと大きいのですが、非 0 が書かれたマスは N 個だけなので座標圧縮すればなんとかなりそうです。

各行と各列の総和を計算し、非 0 が書かれたマスがある行 r を全探索します。各列の総和から (r, c) の値を引いたあと列の総和の最大値を求めます。この最大値と r 行の総和の合計が r 行のあるマスを選んだときの S の最大値であり、解の候補となります。すべての候補のなかから最大値を選べば、これが解となります。