Official
G - Printing Machine Editorial
by
解説
G - Printing Machine Editorial
by
yuto1115
解説
まず、以下のようなアルゴリズムを考えます。
- \(t=1,2,3,\dots,\) の順に以下を行う。
- 時刻 \(t\) に印字機の範囲内にある商品であって、まだ印字されていないものがあるならば、そのうち印字機の範囲から出るのが最も早い商品に印字する。
この貪欲法は正しいです。正当性の厳密な証明は省略しますが、整数時刻以外に行動しても得しないことと、ある最適解がある \(t\) において上記の貪欲法と異なる行動をしていた場合、その行動を貪欲法における行動で置き換えても損しない(最適解であり続ける)ことから示されます。
しかし、\(t\) は最大で \(10^{18}\) 程度になりうるため、このままでは間に合いません。そこで、以下のような高速化を導入します。
- ある時刻 \(t\) において、印字できる商品(すなわち、印字機の範囲内にあってまだ印字されていない商品)が一つもなかったとする。そのような場合は、\(t\) 以降で商品が印字機の範囲内に新たに入ってくる最初の時刻を \(t'\) として、\(t \leftarrow t'\) と更新する。
\(t\) と \(t'\) の間では印字できる商品がずっと存在しないため、正当性は明らかです。この高速化を行うことで、実際に見る \(t\) の個数は \(O(N)\) になります。印字できる商品の中から印字機の範囲を出る時刻が最も早い商品を選ぶのは優先度付きキュー等によって \(O(\log N)\) で、(印字できる商品がない場合に)\(t'\) を求めるのは二分探索や尺取り法によって \(O(\log N)\) や \(O(1)\) でできるため、全体の計算量は \(O(N \log N)\) になります。
実装例 (C++) :
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
int n;
cin >> n;
vector<pair<ll, ll>> v;
for (int i = 0; i < n; i++) {
ll t, d;
cin >> t >> d;
v.emplace_back(t, t + d);
}
sort(v.begin(), v.end());
priority_queue<ll, vector<ll>, greater<>> pq;
int it = 0;
int ans = 0;
for (ll i = 0;; i++) {
if (pq.empty()) {
if (it == n) break;
i = v[it].first;
}
while (it < n and v[it].first == i) {
pq.push(v[it++].second);
}
while (!pq.empty() and pq.top() < i) pq.pop();
if (!pq.empty()) {
pq.pop();
++ans;
}
}
cout << ans << endl;
}
posted:
last update: