H - 清掃サービス/Cleaning Service 解説
by
littlegirl112
取りやめる回数の最小化は、条件を満たしつつサービスを利用する回数(\(K'\))の最大化と同義です。そこで、最終的にサービスを利用する日を昇順に \(D'_1 < D'_2 < \dots < D'_{K'}\) として、\(K'\) の最大化を考えます。このとき、求めたい答えは \(K-K'\)になります。
まず、「どの連続する \(L\) 日間においても利用回数は \(M\) 回以下」という制約は、利用した日の列において以下の条件と同値です。
\[\forall i,\quad D'_{i+M} - D'_i \ge L\]
これは、任意の利用日 \(D'_i\) と、そこから \(M\) 回後の利用日 \(D'_{i+M}\) の間隔が \(L\) 日以上開いていれば、その間の \(M+1\) 回の実施が期間 \(L\) に収まることはない(すなわち条件を満たす)ことを意味します。
この条件のもとで \(K'\) を最大化するには、元の日程 \(D\) を昇順に走査し、利用した日を配列に格納しながら以下の貪欲法を適用すればよいです。
- 配列の要素数が \(M\) 未満ならば採用する。
- そうでない場合、今回の日程 \(x\) を採用しても条件を満たすならば採用する。そうでないならば採用しない。
正当性は \(M+1\) 回利用した際に条件を満たさない場合、明らかに直近のものを採用しないようにして損をしないことから従います。
よって、上記のアルゴリズムを実装すればよく、例えばC++の場合、vector を用いることで実装できます。計算量は \(O(K)\)です。
実装例 (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ll N, K, L, M;
cin >> N >> K >> L >> M;
vector<ll> D(K);
for (auto& x : D) cin >> x;
ll val = 0;
vector<ll> used;
for (auto x : D) {
ll sz = used.size();
if (sz < M or x - used[sz - M] >= L) {
used.push_back(x);
val++;
}
}
cout << K - val << endl;
}
投稿日時:
最終更新:
