AtCoder NoviStepsを埋めてみる(34) セグメント木(segment tree)の続きです。今回もセグメント木(segment tree)です。前回同様 ac-library-csharp の Segtree<T> クラスを使います。

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

D – Flat Subsequence

D – Flat Subsequence

問題の概要

長さ N の数列 A と整数 K が与えられる。
以下の条件を満たす数列 B の長さとして考えられる最大値を出力せよ。

条件
B は A の (連続とは限らない) 部分列である。
どの B の隣り合う要素の差の絶対値も K 以下である。

動的計画法で考えます。

dp[v]: 最後の項の値が v となる場合についての最長の長さ
dp[v] = Math.Max(dp[v], dp.Max(v – K, v + K) + 1);

dp 自体を配列ではなくセグ木にしてしまえば処理を高速化できます。

RangeMaxQuery クラスは A58 – RMQ (Range Maximum Queries) を参照してください。

B58 – Jumping

B58 – Jumping

問題の概要

N 個の足場が横一列に番号順に並んでいる。
足場 1 がスタート地点、足場 N がゴール地点であり、足場 i はスタート地点から X[i] の位置にある。

1 回で L 以上 R 以下の距離を右方向にのみジャンプできるとき、スタートからゴールまで移動するために必要なジャンプ回数の最小値を求めよ。
ただし、与えられる入力ではスタートからゴールまで到達できることが保証されている。

動的計画法で考えます。

二分探索で足場 i へジャンプできる足場の添字の最大値と最小値を求めておきます。

dp[i]: 足場 i へたどりつくためのジャンプの回数の最小値
idx_min: 足場 i へジャンプできる足場の添字の最小値
idx_max: 足場 i へジャンプできる足場の添字の最大値
dp[i] = dp.Min(idx_min, idx_max) + 1;
(dpを ∞ で初期化するが、1 を加算する処理があるのでオーバーフローに注意)

037 – Don’t Leave the Spice(★5)

037 – Don’t Leave the Spice(★5)

香辛料を使う料理が N 種類ある。

料理 i (1 ≦ i ≦ N) の価値は V[i] で、作るときに香辛料を消費する。消費する香辛料の量は L[i] 以上 R[i] 以下の範囲で調節できる。

N 種類の料理から何種類かを選んでひとつずつ作ることで、香辛料の消費量の合計をちょうど W にすることが可能かどうか判定せよ。可能である場合は作る料理の価値の合計としてあり得る最大の値を出力せよ。

動的計画法で考えます。

dp[col]:香辛料の消費量の合計が col のときの料理の価値の合計の最大値
dp[col] = dp.Max(col – r, col – l) + v;
※ dp[col – j] にアクセスするときに配列外アクセスにならないように注意
※ [col – r, col – l] がすべて負数のときは更新できない。
※ col が大きなものから更新することで同じオブジェクトを使い回すことができる。

Q – Flowers

Q – Flowers

問題の概要

N 本の花が横一列に並んでいる。
i 番目の花の高さは H[i] で、美しさは A[i] である。ただし H はすべて異なる値である。

何本かの花を抜き去り、高さが単調増加になるようにしたい。
残りの花の美しさの総和の最大値を求めよ。

動的計画法で考えます。

dp[h]: 最後に選んだ花の高さが h となる場合の選ばれた花の美しさの総和の最大値
dp[h] = dp.Max(0, h – 1) + A[i];

F – Second Largest Query

F – Second Largest Query

問題の概要

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

クエリ 1 : A[p] の値を x に変更する。
クエリ 2 : A[l] ,A[l + 1], …, A[r] において二番目に大きい値の個数を出力する。

構造体を使って 区間 [l, r] における最大値、最大値の個数、二番目に大きい値、二番目に大きい値の個数 の 4 要素を管理すればよいのですが、処理を工夫しないと TLE してしまいます。

F – Parenthesis Checking

F – Parenthesis Checking

問題の概要

Q 個のクエリが与えられるので処理せよ。
クエリ 1:S の l 文字目と r 文字目を入れ替える。
クエリ 2:S の l 文字目から r 文字目までの連続部分文字列が正しい括弧列であるか判定する。

以下を正しい括弧列と定義する。
(1) 空文字列
(2) ある正しい括弧列 A が存在して、(, A, ) をこの順に連結した文字列
(3) ある空でない正しい括弧列 A, B が存在して、A, B をこの順に連結した文字列

括弧列の特徴は以下のとおりです。

① 文字列 S に含まれる(と)の個数は等しい
② 任意の 1 ≦ k ≦ |S| に対して、S の k 文字目までに含まれる ‘(‘ の個数 ≧ S の文字目までに含まれる ‘)’ の個数 が成り立つ

A[i] を S[i] == ‘(‘ なら 1 、S[i] == ‘)’ なら -1 と定義します。そして A の i までの累積和を sums[i + 1] とします。まず、① の性質より sums[l] == sums[r + 1] となっていなければなりません。また ② の性質より min(sums[l + 1], sums[l + 2], …, sums[r + 1]) ≧ sums[l] となっていなければなりません。

クエリに対応するために、区間加算の処理と区間最小値を取得できる遅延評価型セグメント木を構築します。

クエリ 1 への対応ですが、S の l 文字目と r 文字目を入れ替えたときに同じ文字を入れ替えるのであれば何もする必要はありません。異なる文字を入れ替えたときは sums の閉区間 [l + 1, r + 1] が 2 増えるか減るかのどちらかです。クエリ 2 に対しては sums[l] == sums[r + 1] かつ sums.Min(l + 1, r + 1) ≧ sums[l] であるかどうかを調べればよいです。

B59 – Number of Inversions

B59 – Number of Inversions

問題の概要

長さ N の数列 A が与えられる。
1 ≦ i < j ≦ N かつ A[i] > A[j] を満たす整数の組 (i, j) の個数を求めよ。

値 v が A のなかに何個あるかを管理する配列 counts を定義します。

A[i] を順番に読み込み、counts[A[i]] をデクリメントしたあと、counts.Sum(0, A[i] – 1) を求めます。これが i における A[i] > A[j] を満たす整数の組 (i, j) の個数です。0 ≦ i ≦ N – 1 についてすべて足したものが解となります。

F – Double Sum

F – Double Sum

問題の概要

整数列 A が与えられる。次の式を計算せよ。

すべての i に対して i < j かつ A[i] < A[j] を満たす j の個数と A[j] の総和がわかるなら、与式の答えは (A[j] の総和 – j の個数 × A[i]) の総和です。では j の個数 と A[j] の総和はどうやって求めればよいでしょうか?

(i, A[i]) をペアにしたものを A の値で降順ソートします。そして順番に (i, A[i]) を取り出し、counts[i] = 1, sums[i] = A[i] とします。counts において i よりも右側の要素の総和を求めれば i < j かつ A[i] < A[j] を満たす j の個数がわかります。同様に sums において i よりも右側の要素の総和を求めれば i < j かつ A[i] < A[j] を満たす A[j] の総和がわかります。これで (A[j] の総和 – j の個数 × A[i]) がわかるのでその総和を求めれば問題の解が得られます。

J – Segment Tree

J – Segment Tree

問題の概要

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

クエリ 1:A[x] を v で更新する。
クエリ 2:A[j] (l ≦ j ≦ r) の最大値を求める。
クエリ 3:x ≦ j ≦ N, v ≦ A[j] を満たす最小の j を求める。

セグメント木であればクエリ 1 と 2 は簡単にできそうです。実はクエリ 3 も二分探索法を用いれば同じセグメント木の上でできます。

F – Insert

F – Insert

問題の概要

配列 P が与えられる。
空の配列 A に対して i = 1, 2, …, N の順に 数 i を A の前から P[i] 番目の位置になるように挿入していく場合、すべての操作を終えた後の A を出力せよ。

操作を逆順に考えるとうまくいきます。

A’ = (1, 2, …, N) に対し、i = N, N – 1, …, 1 の順に以下の問題を考えます。

A’ の P[i] 番目の要素を削除し、残りの要素は順序を保って詰める。
各操作で削除される数を B[i] とする。これを求めよ。

各操作で削除される数の求め方ですが、長さ N の配列 T を定義し、i が A’ のなかに含まれていたら T[i] = 1、そうでなければ T[i] = 0 とします。最初はすべての要素が 1 です。

A’ の P[i] 番目の要素を削除するときは (T[0] + T[1] + … + T[j]) = P[i] を満たす最小の j を二分探索法で探し、T[j] を 0 に更新して B[i] = j とします。

B を反転して A[B[i]] = i + 1 とすれば操作後の A を求めることができます。

E – A > B substring

E – A > B substring

問題の概要

A, B, C の 3 種類の文字からなる長さ N の文字列 S が与えられる。
S の空でない連続する部分文字列は N × (N + 1) / 2 個存在するが、そのなかに A が B よりも多く含まれるものはいくつあるか求めよ。

S の先頭 i 文字の中にある A の個数を A[i]、B の個数を B[i] とします。

S の i 文字目から j 文字目までを取って得られる部分文字列が条件を満たすことは、A[j] – A[i – 1] > B[j] – B[i – 1] (ただし i <= j)と言い換えることができます。これは式変形をすることで A[j] - B[j] > A[i - 1] - B[i - 1] となります。 C[i] = A[i] - B[i] とおくと、C[i] < C[j](ただし i < j)を満たす (i, j) の個数が求める解となります。B59 – Number of Inversions とは逆の処理(C[i] > C[j] ではなく C[i] < C[j] を数える)をすればよいのですが、C に値の重複がある場合があること、C[i] が負数の場合があるので全体に同じ値を加算して全要素を非負に変換してから処理をしなければならない点に注意が必要です。

F – Manhattan Christmas Tree 2

F – Manhattan Christmas Tree 2

問題の概要

二次元平面上に N 個のクリスマスツリーがあり、i 番目 のクリスマスツリーは座標 (X[i], Y[i]) に存在する。

Q 個のクエリを処理せよ。

クエリ 1:i 番目のクリスマスツリーの座標を (x, y) に変更する。
クエリ 2:L 番目から R 番目までのクリスマスツリーのうち、座標 (x, y) からマンハッタン距離で最も遠いクリスマスツリーまでの距離を出力する。

マンハッタン距離で最も遠い点を取得する方法ですが、いわゆる45度回転の処理をおこないます。

(x1, y1) と (x2, y2) のマンハッタン距離は |x1 – x2| + |y1 – y2| です。

|z| は max(z, -z) と考えることができます。
|x1 – x2| + |y1 – y2|
⇔ max(x1 – x2, x2 – x1) + max(y1 – y2, y2 – y1)
⇔ max((x1 – x2) + (y1 – y2), (x1 – x2) + (y2 – y1), (x2 – x1) + (y1 – y2), (x2 – x1) + (y2 – y1))
⇔ max((x1 + y1) – (x2 + y2), (x1 – y1) – (x2 – y2), -(x1 – y1) + (x2 – y2), -(x1 + y1) + (x2 + y2))

ここで x + y = u, x – y = v と置くと max(u1 – u2, v1 – v2, -v1 + v2, -u1 + u2) となり、絶対値を使うなら max(|u1 – u2|, |v1 – v2|) です。x と y が混在する計算式を置き換えることで u のみの式と v のみの式に分離することができました。

座標 (x0, y0) からマンハッタン距離で最も遠い点の距離は max(|U – u0|, |V – v0|) ですが、これは max(|maxU – u|, |maxV – v|, |minU – u|, |minV – v|) と表すことができます。RangeMaxQuery, RangeMinQuery クラスを使えば L 番目から R 番目までの点のうち、座標 (x, y) からマンハッタン距離で最も遠い点までの距離を求めることができます。

F – Starry Landscape Photo

F – Starry Landscape Photo

問題の概要

N 個の星が 東から西へ一直線上に並んでいる。東から i 番目 の星の明るさは B[i] 番目である。

整数の組 (l, r) を選ぶ。東から l 番目の星から r 番目の星のみがフレームに収まるようにカメラを設置する。
整数 b を選び、星の明るさが b 番目までの星のみが写るようにシャッターを開放する。

このようにして撮影された夜空の写真に写っている星の集合としてありえるものが何通りあるか求めよ。

整数 b を 1 から順に増やしていくことを考えます。これによって写真に写る星がひとつずつ増えていくのですが、その星と既存の星の組み合わせの数がいくつあるかを考えてみることにします。

組み合わせの数はその星の左側にある星の数(left)と右側にある星の数(right)から計算できます。(left + 1) × (right + 1) が組み合わせの数です。これはRangeSumQuery クラスを使えば計算できます。

E – Clamp

E – Clamp

問題の概要

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

クエリ 1:A[x] の値を y に変更する。
クエリ 2:B[i] = max(l, min(r, A[i])) と定義したとき B の総和を求める。

B[i] の値は以下のようになります。

① l < r のとき
A[i] ≦ l ⇒ B[i] = l、A[i] ≧ r ⇒ B[i] = r、それ以外 ⇒ B[i] = A[i]

② それ以外のとき
常に B[i] = l

① を計算するには A[i] の値が v である i の個数を管理します。

counts[v]:A[i] の値が v である i の個数
sums[v]:A[i] の値が v である A[i] の総和

RangeSumQuery クラスを使えば l 以下の要素の個数、r 以上の要素の個数、l より大きく r より小さい要素の総和を計算することができます。

E – Alternating String

E – Alternating String

問題の概要

0 と 1 のみからなる長さ N の文字列 S が与えられる。
Q 個のクエリを処理せよ。

クエリ 1:S の L 文字目から R 文字目までの 0 と 1 を反転させる。
クエリ 2:S の L 文字目から R 文字目までを抜き出した部分文字列において、どの連続する 2 文字も異なるのであれば “Yes”、そうでないなら “No” と出力する。

文字列の先頭に番兵 ‘X’ を追加します。そして A[i] を S[i] == S[i + 1] なら 1、そうでないなら 0 と定義します。

L 文字目から R 文字目までの 0 と 1 を反転させた場合、A の値が変更されるのは A[L – 1] と A[R] だけです。

また L 文字目から R 文字目(L < R)までを抜き出した部分文字列において、連続する 2 文字が同じである部分があるなら 閉区間 [L, R – 1] における A の最大値は 1 であり、そうでないなら 0 です(L == R のときは常に答えは “Yes” になるので注意すること)。