問題
個の要素からなる整数列 が与えられる。
これをいくつかの空でない部分列に分割する。 番目の部分列の要素の合計を とし、部分列の個数を 個とするとき、 の最大値を求めよ。
例
例えば、 とし、この数列 を と分割したとき、 \begin{align} \sum_{i = 1}^{k} i \cdot \mathrm{sum}_{i} = 1 \cdot (1) + 2 \cdot (-3 + 7) + 3 \cdot (-6 + 2) + 4 \cdot (5) = 17 \end{align} と計算される。
入力
まず最初の1行目に、テストケースの個数を表す整数 が与えられる。
その後、 個のテストケースのそれぞれに対して、以下のように入力が与えられる。
- 最初の1行目には、整数 が与えられる。
- 次の1行には、 個の整数 が順に与えられる。
条件
-
- すべてのテストケースでの の値の合計は を超えない。
出力
各テストケースにおける最大値を1行ずつ出力すること。
解法
数列 の要素すべてが 以上であるとき、合計値として最大となるのは \begin{align} 1 \cdot {a}_{1} + 2 \cdot {a}_{2} + \cdots + (n - 1) \cdot {a}_{n - 1} + n \cdot {a}_{n} \end{align} のときです。 すなわち、数列 を と各要素を1つの部分列として扱うときに、求める値が最大となります。
これは、例えば数列 を のように、 と を1つの部分列として扱ったときの値は \begin{align} 1 \cdot {a}_{1} + \cdots + (i - 1) \cdot {a}_{i - 1} + i \cdot ({a}_{i} + {a}_{i + 1}) + (i + 1) \cdot {a}_{i + 2} + \cdots + (n - 1) \cdot {a}_{n} \end{align} となることから、すべての要素を1つの部分列として扱うときの値と差を取ると、 \begin{align} & \ (1 \cdot {a}_{1} + \cdots + n \cdot {a}_{n}) \\ - & \ \{ 1 \cdot {a}_{1} + \cdots + (i - 1) \cdot {a}_{i - 1} + i \cdot ({a}_{i} + {a}_{i + 1}) + (i + 1) \cdot {a}_{i + 2} + \cdots + (n - 1) \cdot {a}_{n} \} \\ = & \ {a}_{i + 1} + {a}_{i + 2} + \cdots + {a}_{n} \end{align} となり、これは0以上の値となります。
従って、すべての に対して が成立するときは、各要素を1つの部分列とするときが最大となります。 すなわち、なるべく要素数を増やし、掛けられる の値を大きくすることによって、最大値が得られるということが言えます。
一方で、今回の問題では である場合も存在します。
ここで、数列 の 番目以降の要素について、分け方が決定していると仮定します。 ( の 番目から 番目の要素については、すべて要素数1の部分列として分割されているものとします。) このときの求める値は \begin{align} 1 \cdot {a}_{1} + 2 \cdot {a}_{2} + \cdots + i \cdot {a}_{i} + (i + 1) \cdot ({a}_{i + 1} + \cdots) + \cdots \end{align} となります。
このとき、 をどの部分列に所属させるかということについては、
- として独立させる
- 隣の部分列 と統合して、 とする
の2通りの方法が考えられます。
このとき1つ目の方法については、分け方について変化はないので、値についても変わりはありません。
一方で2つ目の方法については、 番目以降の部分列の番号が1つずつ変わります。 ( の所属する部分列の番号が から となります。)
そのため、元の値と比較すると、 \begin{align} & \ \{ 1 \cdot {a}_{1} + \cdots + (i - 1) \cdot {a}_{i - 1} + i \cdot {a}_{i} + (i + 1) \cdot ( {a}_{i + 1} + \cdots ) + \cdots \} \\ - & \ \{ 1 \cdot {a}_{1} + \cdots + (i - 1) \cdot {a}_{i - 1} + i \cdot ({a}_{i} + {a}_{i + 1} + \cdots ) + \cdots \} \\ = & \ {a}_{i + 1} + \cdots + {a}_{n} \end{align} となります。
以上から、 の値が0以上である場合は元の値の方が大きくなり、そうでない場合は新しい値の場合の方が大きくなります。 従って、この値が0以上のときは として独立させ、そうでないときは として隣の部分列に統合することにより、求める値をより大きくしていくことができます。
これを の順で行っていくことにより、求める値の最大値を求めることができます。
ここで、値 と定義すると、 \begin{align} {a}_{i + 1} + \cdots + {a}_{n} = \mathrm{total}_{n} - \mathrm{total}_{i} \end{align} が成立するため、 の値を で求めることができます。
従って、この値 の準備を予め行うことにより、各テストケースに対して の計算量で答えを出力することができます。
ソースコード
与えられた数列に対して、最大値を返す関数 solve()
を以下のように実装しました。
int n; ll a[int(1e5 + 5)], total[int(1e5 + 5)]; ll solve() { // 値の入力 cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; } // 数列 total の準備 for (int i = 1; i <= n; i++) { total[i] = total[i - 1] + a[i]; } ll ans = 0; // 答えとなる値 for (ll i = 1; i <= n; i++) { ans += i * a[i]; // まずは、1要素ずつが部分列の場合の値を求める } for (int i = n - 1; i >= 1; i--) { ans = max(ans, ans - (total[n] - total[i])); // a[i] を今のままにする場合と次の部分列に入れる場合との値を求め、大きい方を ans として更新する } return ans; }