競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 153F

ABC153F

まずは,爆弾を使う座標について考える. ダメージの入る範囲を \([x-d, x+d]\) でなく, \([x', x'+2d]\) と考える. 中途半端な位置で爆弾を使うメリットは無いため, 爆弾を使うときに左端に敵が居ると仮定してよい. 言い換えれば,左端に敵が居ない使い方をした場合は, 左端に敵がいる場所に置き換えた最適解が存在する.
爆弾を使う座標 \(x\) を与えたとき, ダメージの入る座標の範囲は \([x, x+2d]\) であるから, ここに居る敵を高速に求めたい. これは,敵を座標によりソートしておけばよい. 左端の敵から,爆弾を使うか全探索していく.

次にダメージ処理する. 爆弾を使ったとき,愚直に \([x,x+2d]\) の範囲にダメージを加算すると, 範囲が大きいときに全体で \(O(N^2)\) となる. そこで,ダメージの累積を求めておいて,実際にダメージを反映させるのを保留しておく.
これは imos 法で実装できる. \(i \in [0,N), j \in [1,N+1)\) に対して, 区間 \([i,j)\) に対してダメージ \(dmg\) を与えるとき, \(ev[i] += dmg, ev[j] -= dmg\) とすればよい. \(ev_{i}\) が,モンスター \(i\) に与えるダメージの累計となる.
今調べているモンスターを \(i \in N\) とする. 先にダメージを処理して,まだ体力が残っていたら モンスター \(i\) を倒すのに必要最低限の回数だけ爆弾を使えばよい. つまりソートさえしておけば,使う回数が貪欲に求まる.

使っている記号,マクロ等 "https://ecsmtlir.hatenablog.com/entry/2022/12/23/131925"

int main() {
  ll n,d,a;
  cin >> n >> d >> a;

  vector<pll> p(n);
  rep(i,n){
    cin >> p[i].first >> p[i].second;
  }
  sort(all(p));

  ll ans = 0;
  vll ev(n+1);
  rep(i,n){
    auto [x,h] = p[i];
    h -= ev[i];

    if(h > 0){
      ll num = ceil(h, a);
      ll dmg = a * num;
      auto it = upper_bound(all(p), pll(x + 2*d, INFL)); // rem : case : equal x + 2d
      ans += num;
      ev[i] += dmg;
      ev[it - p.begin()] -= dmg;
    }

    ev[i+1] += ev[i];
  }
  assert(ev[n] == 0);
  cout << ans << endl;

  return 0;
}