D - コンボボーナス / Combo Bonus Editorial by admin
gpt-6-astra-medium概要
各ステージをクリアするかどうかを選び、ステージの得点と隣接するステージ間のコンボボーナスの合計を最大化します。「直前のステージをクリアしたかどうか」を状態とする動的計画法で解けます。
考察
各ステージには「クリアする」「クリアしない」の \(2\) 通りの選択があるため、すべての組み合わせを調べると \(2^N\) 通りになります。\(N \le 10^6\) では間に合いません。
また、単に \(A_i\) が正のステージだけをクリアする方法も正しくありません。負の得点でも、コンボボーナスによってクリアしたほうが有利になる場合があります。
例えば、\(C=5\)、\(A=(3,-2,3)\) の場合を考えます。
- ステージ \(1,3\) だけをクリアすると、スコアは \(3+3=6\)
- すべてクリアすると、スコアは \(3-2+3+5+5=14\)
このように、各ステージの得点だけでは判断できません。
ここで、ステージ \(i\) をクリアするとき、新しく発生する可能性があるボーナスは、ペア \((i-1,i)\) に対するものだけです。したがって、直前のステージをクリアしたかどうかが分かれば、それ以前の選択を細かく覚える必要はありません。
直前の状態が同じなら、今後の得点の増え方も同じなので、それまでのスコアが最大のものだけを残せます。
アルゴリズム
ステージを左から順に処理し、次の \(2\) つの値を管理します。
skip:処理済みの最後のステージをクリアしない場合の最大スコアtake:処理済みの最後のステージをクリアする場合の最大スコア
1. 初期化
まだステージを処理していないときは、次のようにします。
skip\(=0\)take\(=-\infty\)
何もクリアしていない状態のスコアは \(0\) です。一方、「直前のステージをクリアした状態」は存在しないため、take を十分小さい値にします。
2. 各ステージでの更新
ステージ \(i\) の得点を \(A_i\) とします。
ステージ \(i\) をクリアしない場合
直前のステージをクリアしたかどうかに関係なく、得点もボーナスも増えません。したがって、
\(\mathrm{next\_skip}=\max(\mathrm{skip},\mathrm{take})\)
となります。
ステージ \(i\) をクリアする場合
直前のステージの選択によって、次の \(2\) 通りがあります。
- 直前をクリアしていない:
skipに \(A_i\) を加える - 直前をクリアしている:
takeに \(A_i+C\) を加える
よって、
\(\mathrm{next\_take}=\max(\mathrm{skip},\mathrm{take}+C)+A_i\)
となります。
この \(2\) つを計算した後、skip と take をそれぞれ更新します。
3. 答え
すべてのステージを処理した後、最後のステージをクリアする場合としない場合のうち、大きいほうを出力します。
\(\mathrm{answer}=\max(\mathrm{skip},\mathrm{take})\)
各更新では、現在のステージをクリアするかどうかと、直前をクリアしたかどうかの全パターンを考慮しています。そのため、各状態の最大スコアが正しく保たれ、最後に全体の最大値が得られます。
計算量
- 時間計算量: \(O(N)\)
- 空間計算量: \(O(1)\)
各ステージを一度ずつ処理します。入力を読みながら更新できるため、配列 \(A\) を保存する必要はありません。
実装のポイント
スコアには 64 bit 整数を使います。
スコアは最大で \(N\cdot 10^9+(N-1)\cdot 10^9\) 程度になり、32 bit 整数には収まりません。コードではlong longを使用しています。不可能な状態には十分小さい値を使います。
コードでは \(-\infty\) の代わりに-(1LL << 60)を使っています。制約内のスコアより十分小さく、得点やボーナスを加えてもオーバーフローしません。更新前の値を使って、次の状態を両方計算します。
skipを先に書き換えると、next_takeの計算に誤って更新後の値を使ってしまいます。next_skipとnext_takeを計算してから代入しましょう。何もクリアしない選択も自然に扱えます。
skipを \(0\) で初期化し、クリアしない選択を続けられるため、答えが負になることはありません。ソースコード
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
long long C;
cin >> N >> C;
long long skip = 0;
long long take = -(1LL << 60);
for (int i = 0; i < N; ++i) {
long long A;
cin >> A;
long long next_skip = max(skip, take);
long long next_take = max(skip, take + C) + A;
skip = next_skip;
take = next_take;
}
cout << max(skip, take) << '\n';
return 0;
}
この解説は gpt-6-astra-medium によって生成されました。