B - 散水スプリンクラー / Irrigation Sprinkler 解説 by admin
gemini-3.5-flash-high概要
この問題は、数直線上に並んだ \(N\) 個の植え込みに対して、長さ \(D\) の区間(半開区間 \([s, s+D)\))をうまく配置し、区間に含まれる花の本数の合計を最大化する問題です。 「水やりできない花の本数の最小値」は、「すべての花の本数の総和」から「水やりできる(区間に含まれる)花の本数の最大値」を引くことで求められます。
考察
1. スプリンクラーの設置位置の候補を絞り込む
スプリンクラーの設置位置 \(s\) は \(0\) 以上の任意の実数ですが、無限にある実数すべてを探索することは不可能です。 そこで、最適な設置位置の性質を考えます。
ある範囲 \([s, s+D)\) にいくつかの植え込みが含まれているとき、スプリンクラーの位置 \(s\) を右に動かしていくと、いずれかの植え込みが左端から外れる直前、すなわち \(s\) がいずれかの植え込みの座標 \(X_l\) と一致する瞬間 が存在します。 スプリンクラーの左端を植え込みの座標 \(X_l\) にぴったり合わせる(\(s = X_l\) とする)ことで、左端の植え込みをカバーしつつ、右端をできるだけ右側に伸ばすことができます。
したがって、探索すべきスプリンクラーの開始位置 \(s\) は、いずれかの植え込みの座標 \(X_l\) (\(1 \leq l \leq N\))に一致するときのみを考えれば十分です。
2. 素朴なアプローチとその問題点
各植え込み \(l\) を左端としたとき、右端 \(r\) は \(X_r < X_l + D\) を満たす最大の植え込みとなります。 この範囲 \([l, r]\) に含まれる花の本数の総和を、すべての \(l\) について個別に計算するとどうなるでしょうか。
1つの \(l\) に対して \(r\) を探して合計を求めるのに \(O(N)\) の計算量がかかるため、全体で \(O(N^2)\) の計算量になります。 本問題の制約は \(N \leq 2 \times 10^5\) であるため、この方法では実行時間制限に間に合いません(TLEとなります)。
3. しゃくとり法による高速化
ここで、植え込みの座標が \(X_1 < X_2 < \cdots < X_N\) と昇順に並んでいることに注目します。 左端 \(l\) を右に動かしたとき、条件 \(X_r < X_l + D\) を満たす右端 \(r\) もまた、左に戻ることはなく右にのみ移動します(単調性)。
この性質を利用して、しゃくとり法(Two Pointers) を適用します。 左端 \(l\) を \(1\) つ進めるごとに、右端 \(r\) を条件を満たす限り右に進め、区間内の花の本数の合計を差分更新していくことで、各 \(l\) に対する最大値を効率よく求めることができます。
アルゴリズム
初期化:
- すべての花の本数の総和
total_Pを計算します。 - 区間内の花の本数の合計を表す変数
current_sum、その最大値を保持するmax_coveredを \(0\) で初期化します。 - 右端を指すポインタ
rを \(0\) に設定します。
- すべての花の本数の総和
しゃくとり法:
- 左端のインデックス
lを \(0\) から \(N-1\) まで順に進めます。 rが \(N\) 未満、かつ \(X_r < X_l + D\) を満たす間、current_sumに \(P_r\) を加え、rを \(1\) 進めます。- 条件を満たさなくなったら、その時点での
current_sumとmax_coveredを比較し、最大値を更新します。 - 次の
lに進む前に、現在の左端の花の本数 \(P_l\) をcurrent_sumから引きます。
- 左端のインデックス
答えの出力:
total_P - max_coveredが、水やりできない花の本数の最小値となります。
計算量
- 時間計算量: \(O(N)\)
lとrはそれぞれ \(0\) から \(N\) まで最大で \(1\) 回ずつしか増加しません。したがって、しゃくとり法のループ全体での遷移回数は高々 \(2N\) 回となり、全体で \(O(N)\) の時間計算量で処理できます。
- 空間計算量: \(O(N)\)
- 入力された座標 \(X\) と花の本数 \(P\) を保持するための配列のメモリのみが必要となります。
実装のポイント
オーバーフローへの対策:
- 花の本数 \(P_i\) は最大で \(10^9\)、植え込みの数 \(N\) は最大で \(2 \times 10^5\) です。花の本数の総和は最大で \(2 \times 10^{14}\) に達するため、通常の32ビット整数型(C++の
intなど)ではオーバーフローします。合計値を扱う変数(total_P,current_sum,max_covered)には必ず64ビット整数型(C++のlong long)を使用してください。
- 花の本数 \(P_i\) は最大で \(10^9\)、植え込みの数 \(N\) は最大で \(2 \times 10^5\) です。花の本数の総和は最大で \(2 \times 10^{14}\) に達するため、通常の32ビット整数型(C++の
半開区間の条件:
スプリンクラーの散水範囲は \(s \leq X_i < s + D\)(\(s+D\) は未満)です。そのため、しゃくとり法の継続条件は
X[r] < X[l] + Dとなり、等号を含めないように注意する必要があります。ソースコード
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 高速な入出力
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N;
long long D;
if (!(cin >> N >> D)) return 0;
vector<long long> X(N);
vector<long long> P(N);
long long total_P = 0;
for (int i = 0; i < N; ++i) {
cin >> X[i] >> P[i];
total_P += P[i];
}
long long max_covered = 0;
int r = 0;
long long current_sum = 0;
// しゃくとり法により、各左端 l に対してカバーできる最大の右端 r を求める
for (int l = 0; l < N; ++l) {
while (r < N && X[r] < X[l] + D) {
current_sum += P[r];
r++;
}
max_covered = max(max_covered, current_sum);
current_sum -= P[l];
}
long long ans = total_P - max_covered;
cout << ans << "\n";
return 0;
}
この解説は gemini-3.5-flash-high によって生成されました。
投稿日時:
最終更新: