公開日 :
更新日 :
「列のうち i 番目だけ変えたとき全体でどうなるか? 全ての i で答えよ」系の問題は、前後から累積和を取るとうまくいくことがある。
「i 番目より前」と「i 番目より後ろ」の結果を表にしておき、i ごとに 2 つをつなげる。
i ごとに全体を計算し直すと O(N²) だが、表があれば 1 つの i が O(1) になり、全体で O(N)。
問題文が次の形なら、この方法を疑う。
前半の結果と後半の結果から、全体の結果が O(1) で作れるとき使える。
最大・最小・GCD は「1 個足す」はできるが「1 個抜く」はできない。
だから「全体から A_i を抜く」ではなく、前と後ろを別々に作ってつなげる。
(和だけは引き算で抜けるので、全体 − A_i でも済むことがある)
| 形 | i ごとの答え | 例 |
| i を取り除く | 前[i -1]と後ろ[i+1]をつなげる | ABC125 C(1つを好きな数に変える = 実質取り除く) |
| i を境目に左右で別の数え方をする | 前[i – 1]と後ろ[i+1]を足す | ABC098 C(左は W を数え、右は E を数える) |
| i を変えると後ろ全体がずれる | 前[i – 1] と(後ろ[i]をずらしたもの)をつなげる | ABC483 D(後ろの位置がすべて -2A_i ずれる) |
端が固定されている側から作り始めると、範囲が 1 個ずつ増えるだけになる。
| 表 | 範囲 | 固定されている端 | 作る向き |
| 前[i] | 先頭 〜 i | 先頭 | 前から(i を増やす) |
| 後ろ[i] | i 〜 最後 | 最後 | 後ろから(i を減らす) |
後ろ[i] = op(後ろ[i+1], A[i]); // すでにある後ろ[i+1] に A[i] を 1 個足すだけ前から後ろ[i] を作ろうとすると、範囲が左から 1 個ずつ減っていく。
最大などは「抜く」ができないので、毎回数え直しになり \(O(N^2)\)。
先頭と最後に 1 つずつ余分な場所を作り(1-indexed で 0 と N+1)、そこに何もない範囲の値を入れておく。
| 操作 | 何もない範囲の値 |
| 最大 | LLONG_MIN(どの値にも負ける) |
| 最小 | LLONG_MAX(どの値にも勝つ) |
| 和 | 0 |
| GCD | 0 (gcd(0, x) = x) |
vector<ll> L(N + 2, 何もない値), R(N + 2, 何もない値);
for (int i = 1; i <= N; i++) L[i] = op(L[i-1], A[i]); // 前から
for (int i = N; i >= 1; i--) R[i] = op(R[i+1], A[i]); // 後ろから
for (int i = 1; i <= N; i++) {
ll v = op(L[i-1], R[i+1]); // i を除いた全体
ans = max(ans, v);
}