ABC 438 D 問題:後方累積最大値(末尾累積 max)の扱い方
はじめに
本記事では 後方累積最大値 (末尾累積 max)の構成法と使い方の型を説明します。
まずは累積和に関する用語の整理と定義を行い、例題で後方累積最大値の使い方を見ていきます。続いてこのデータ構造が持つ性質を確認し、最終的に ABC 438 D 問題への応用に繋げるという流れで進めます。
初期値の設定や添字の扱いで迷わずに扱えるようになることが本記事の目標です。
なお ABC 438 D 問題の詳細な解説は本記事では行いませんので、必要な場合は公式解説などと併せてお読みください。
累積和と後方累積和
用語整理のため、まずは累積和・後方累積和について軽く触れます。
以降、記事内の添字はすべて 0-indexed です。
累積和(前方累積和)はリストの先頭から順に和を取ったデータ構造です。
また、問題によっては末尾から順に和を累積したデータ構造が必要になる場合があります。こうしたデータ構造は末尾累積和や後方累積和と呼ばれますが、本記事では 後方累積和 と統一します。
後述の累積最大値との対比のため、数列
先頭(末尾)に
累積最大値と後方累積最大値
続いて累積最大値の定義を述べ、以降は本題である後方累積最大値に焦点を当てていきます。
累積和の考え方を一般化し、先頭から順に
さらにこれを末尾から順に累積したものが 後方累積最大値
累積和で
この形式で定義することで、以下に示す性質が成り立つようになります。
後方累積最大値の性質
-
の長さはS_{\rm{max}} である。N + 1 -
はS_{\rm{max}}[i] 以降(A_i を含む) の値の最大値である。i -
が成り立つ。S_{\rm{max}}[0] = \max(A) -
はS_{\rm{max}}[N] (または十分小さい値)である。-\infty -
について常にi \lt j が成り立つ。S_{\rm{max}}[i] \ge S_{\rm{max}}[j]
注意すべき点として、
では、次節でこれらの性質を用いた例題を見ていきます。
例題:各位置より後方にある要素の最大値をすべて求める
- 長さ
の数列N が与えられる。数列内の位置A について、0 \le i \lt N - 1 より後方の最大値i を求め、その値を\max_{i \lt j \lt N}(A_j) の昇順に出力せよ。i - 制約は以下のとおり。
2 \le N \le 10^6 -10^6 \le A_i \le 10^6 \quad (0 \le i \lt N)
この問題は後方累積最大値を前計算することにより計算量
- まず後方累積最大値
を末尾から順に計算する。S_{\rm{max}} - 次に
に対して0 \le i \lt N - 1 を出力する。S_{\rm{max}}[i + 1]
この例題に対する解法を Python で書くと以下のようになります。
n = int(input())
a = list(map(int, input().split()))
# 後方累積最大値の前計算
INF = 10**18
smax = [-INF] * (n + 1)
for i in range(n - 1, -1, -1):
smax[i] = max(smax[i + 1], a[i])
# 解の出力
for i in range(n - 1):
print(smax[i + 1])
最後に、このように「数列のある位置より後方の最大値を高速に求められる」ことを ABC 438 D 問題に応用した例を紹介します。
ABC 438 D 問題
問題全文:D - Tail of Snake
以下の内容は問題原文を 0-indexed に置き換えています。
長さ
ただし
解法はいくつか考えられますが、本記事では「
この「最適な
解法の概要
まず
ここで
以降は
そこで以下のリスト
制約から
以上より、
おわりに
後方累積最大値に限らず、添字を扱う際は 0-indexed または 1-indexed のどちらかに統一することをおすすめします。基本的には 0-indexed に揃えるのが楽でしょう。
累積和の場合は初期値
ミスを減らす一番の方法は典型に落とし込むことです。この記事では特に添字が複雑になりがちな後方累積において明確な型を示しました。
本記事では深く触れませんでしたが、後方累積和もまた強力なデータ構造です。
左から走査して解けそうな問題で後方の最適値を求める必要が出てきた際は、後方累積和・後方累積最大値が使えるかもしれないと考えてみてください。
Discussion