BEN2のブログ

たまに書いています。

ABC 095 D - Static Sushi

ABC 095 D - Static Sushi についてのメモを残します。

人間回転寿司

下図は、初期位置  O から反時計回りに歩き、 B で U ターンし、 O に戻ってきて、今度は時計回りに歩き、 A で退店する様子を表している。

f:id:BEN2suzuka:20200601170920p:plain

下図のように、 O A O B という歩き方もある。

f:id:BEN2suzuka:20200601170941p:plain

いずれも  A B は、寿司の位置または  O に一致しなければならず、寿司の位置でも  O でもない地点で折り返したり退店したりするのは、カロリーの無駄遣いである。

まずは、前者の  O B O A で考える。
 A B の位置を決めることで正味の摂取カロリーを計算できるが、素朴な全探索では  O(N^{2}) で間に合わない。
 B の位置を  x_{b} としたとき、正味の摂取カロリーが最大となるような  A の位置を高速に求める方法はないだろうか。
ここで、 O から時計回りに歩き、道中の寿司を食べながら  x_{a} の位置に到達したときの  O x_{a} 間の正味の摂取カロリーを  f(x_{a}) = v_{1} + v_{2} + \cdots + v_{a} - x_{a} とする。
ただし、初期位置  O x_{0} とし、 f(x_{0}) = 0 とする。

f:id:BEN2suzuka:20200601170930p:plain

 B の位置が  x_{b} のとき、 A の位置の候補は  x_{0}, \, x_{1}, \cdots , \, x_{b-1} であり、 f(x_{0}), \, f(x_{1}), \, \cdots , \, f(x_{b-1}) のうち最大値をとる  x_{a} A の位置として採用するとよい。
ただし、ここでは最大値さえ分かればよい。

このようにして、 B の位置で全探索し、正味の摂取カロリーの最大値を求める。
同様に、 O A O B の場合についても行う。

実装

寿司のカロリーの累積和、 f(x_{a}) の累積和ならぬ "累積 max" なるものを用意しておくとよさそう。

#include <bits/stdc++.h>
using namespace std;

int main() {
  long long N, C; cin >> N >> C;
  vector<long long> X(N), V(N);
  for (int i = 0; i < N; i++) cin >> X.at(i) >> V.at(i);

  // sumV[i] := [0, i) の寿司の栄養価の合計
  vector<long long> sumV(N+1, 0);
  for (int i = 0; i < N; i++) sumV.at(i+1) = sumV.at(i) + V.at(i);

  // 初期位置から時計回りに i 個目まで食べてもよいとき、
  // 正味の摂取カロリーの最大値
  vector<long long> maxA(N+1, 0);
  long long tmpA = 0;
  for (int i = 1; i <= N; i++) {
    tmpA = max(tmpA, sumV.at(i) - X.at(i-1));
    maxA.at(i) = tmpA;
  }

  // 初期位置から反時計回りに i 個目まで食べてよいとき、
  // 正味の摂取カロリーの最大値
  vector<long long> maxB(N+1, 0);
  long long tmpB = 0;
  for (int i = 1; i <= N; i++) {
    tmpB = max(tmpB, sumV.at(N) - sumV.at(N-i) - (C - X.at(N-i)));
    maxB.at(i) = tmpB;
  }

  long long ans = 0;
  // まず反時計回りに i 個目まで食べて、引き返して、
  // 時計回りに適切な個数食べる
  for (int i = 0; i <= N; i++) {
    long long cost = i ? (2 * (C - X.at(N-i))) : 0;
    ans = max(ans, sumV.at(N) - sumV.at(N-i) - cost + maxA.at(N-i));
  }
  // まず時計回りに i 個目まで食べて、引き返して、
  // 反時計回りに適切な個数食べる
  for (int i = 0; i <= N; i++) {
    long long cost = i ? (2 * X.at(i-1)) : 0;
    ans = max(ans, sumV.at(i) - cost + maxB.at(N-i));
  }
  cout << ans << endl;
}