問題
個の建物を回増築することを考える。建物は
- 最初の価格はである
- 増築するときには現在の価格に等しい費用がかかる
- 1回増築すると価格が上昇する
回増築するのに必要な価格の合計値の最小値を求めよ。
解法
解法の流れは、まず最後に増築するときの費用がであるような場合について、増築回数が回を超えられるかどうかを判定することでについての2分探索をする。そして求まったに基づいてシミュレーションして答えを得る。
最後に増築するときの費用がであるとき、各建物については価格が未満であるときはどこかの段階で必ず増築し、ちょうどであるときは最後に増築するかしないか選択の余地がある。結局、価格がを超えるような増築回数が可能な増築回数であり、についてそれらの和を考えればに対しての可能な増築回数が求まる。それがを超えていればそのを最後の増築費用として行うことができ、これは単調性があるので2分探索が可能である。
シミュレーションはほぼ上と同様であり、について各建物について価格がを超えるように増築し、費用の総和を求めながら増築回数を数えていく。最後に増築回数の総和がを超えていれば超過分の操作におけるコストはなのでそれをかけた値を引いて答えとなる。超過分の操作コストが未満ということも超過であるということもないのでそのまま引いてよい。
反省
1時間34分(8WA)でAC。部分点解法のpriority_queue
を使ってというのはすぐ見えたし、満点解法としても答えを直接ではなく違う部分を2分探索するというアイデアには至っていたがまとめきれず、1時間過ぎたあたりで解説を読んだ。
解説のように操作のコストの方に着目する方が良く、最終的な建物の価格で考えていたので混乱してしまった。回を超えたりすることがあるのでそこを上手く処理しなければならなかったのだが……。
解説を理解してからも初期化のミスで4WA出してしまった。INT_MAX
では小さすぎるのと、LLONG_MAX
では大きすぎるというのを両方やってしまったが、内部の判定で回足されるため限界値よりは多少小さめにして[tex:1015]やLLONG_MAX / N
としなければならなかったのだった。あまりにこういうミスが多いので、constexpr ll INF = (ll)1e15
とでもしておいた方がいいかもしれない。using ll = int64_t
も抵抗があってなかなか入れなかったが、こういう本質的でないミスを回避できるとすれば多少の気持ち悪さには目をつぶるべきか。
コード
#include"bits/stdc++.h" using namespace std; using ll = int64_t; ll K, N; vector<ll> A, D; bool isOK(ll price) { //最後の増築がprice円である場合に増築回数がK回を超えられるか ll num = 0; for (ll i = 0; i < N; i++) { //各建物についてprice円超過となるように増築する回数 if (A[i] > price) { continue; } num += (price - A[i]) / D[i] + 1; } return (num >= K); } int main() { cin >> K >> N; A.resize(N); D.resize(N); for (ll i = 0; i < N; i++) { cin >> A[i] >> D[i]; } ll ok = LLONG_MAX / N; ll ng = 0; while (ng + 1 != ok) { ll mid = (ok + ng) / 2; assert(ng <= mid && mid <= ok); (isOK(mid) ? ok = mid : ng = mid); } ll ans = 0; ll num = 0; for (ll i = 0; i < N; i++) { //各建物についてprice円以上となるように増築する if (A[i] > ok) { continue; } ll time = (ok - A[i]) / D[i] + 1; num += time; //ans += (A[i] + A[i] + D[i] * (time - 1)) * time / 2; ans += A[i] * time + time * (time - 1) / 2 * D[i]; } //足し過ぎた分を引く ans -= ok * (num - K); cout << ans << endl; }