C - 花壇の水やり / Watering the Flower Bed Editorial by admin
gemini-3.5-flash-thinking概要
この問題は、長さ \(N\) の数列 \(A\) を、長さ \(K\) の連続する区間に \(1\) を加算する操作を繰り返すことで、目標の数列 \(B\) に一致させることができるかを判定し、可能な場合はその最小操作回数を求める問題です。
左端から順番に値を確定させていく「貪欲法(Greedy)」と、区間内の加算量を高速に管理する「スライディングウィンドウ」の手法を用いることで、 \(O(N)\) の時間計算量で効率的に解くことができます。
考察
1. 差分への注目
まず、各区画 \(i\) について必要な増加量を \(D_i = B_i - A_i\) とします。
操作によって水分量を「減少」させることはできないため、もし \(D_i < 0\) となる区画が \(1\) つでも存在する場合、目標を達成することは不可能です。したがって、この時点で -1 を出力して終了します。
以降は、すべての \(i\) について \(D_i \ge 0\) であると仮定します。
2. 左端から貪欲に決める
操作は「区画 \(l\) から始まる長さ \(K\) の区間に \(1\) を加算する」というものです。 一番左の区画 \(i = 0\)(0-indexed)に注目してみましょう。区画 \(0\) をカバーできる操作は、区画 \(0\) を左端とする操作(\(l = 0\))しかありません。 したがって、区画 \(0\) を目標値にするためには、区画 \(0\) を左端とする操作をちょうど \(D_0\) 回行う必要があります。これより多くても少なくてもいけません。
この操作を行うと、区画 \(0\) から \(K-1\) までの水分量がすべて \(D_0\) だけ増加します。
次に、区画 \(1\) に注目します。
区画 \(1\) はすでに先ほどの操作によって \(D_0\) だけ水分量が増加しています。したがって、区画 \(1\) を左端とする操作を行うべき回数は、残りの必要量である \(D_1 - D_0\) 回に一意に決定されます。
もし \(D_1 - D_0 < 0\) となってしまった場合、すでに区画 \(1\) の水分量が目標値を超えてしまっていることを意味し、これ以上減らすことはできないため、達成不可能(-1)となります。
このように、左端から順番に「その区画の不足分を補うために、その区画を左端とする操作を何回行うか」を貪欲に決めていくことができます。
3. 操作のシミュレーションの高速化
一般に、区画 \(i\) を処理するとき、それまでに決定した操作によって区画 \(i\) にすでに加算されている値の合計を \(S_i\) とします。 区画 \(i\) を左端とする操作回数を \(x_i\) とすると、以下のように決定できます。
- \(i \le N - K\) のとき(右側に長さ \(K\) の区間を確保できる場合)
- \(x_i = D_i - S_i\) と決定します。
- もし \(x_i < 0\) であれば、目標値を超過しているため達成不可能です(
-1)。
- \(i > N - K\) のとき(右側に長さ \(K\) の区間を確保できない場合)
- この区画を左端とする操作は行えません(\(x_i = 0\))。
- したがって、すでに加算されている値 \(S_i\) が、目標の差分 \(D_i\) とちょうど一致している必要があります(\(D_i = S_i\))。一致していなければ達成不可能です(
-1)。
ここで、 \(S_i\) を毎回愚直に計算すると、直近 \(K\) 個の \(x\) の和を求める必要があり、全体で \(O(NK)\) の時間がかかってしまいます。 しかし、区画 \(i\) に影響を与える操作は \(x_{i-K+1}\) から \(x_{i-1}\) までの \(K-1\) 個です。 したがって、 \(S_i\) は一歩手前の状態 \(S_{i-1}\) を用いて、 $\(S_i = S_{i-1} + x_{i-1} - x_{i-K}\)\( と表すことができます。 このスライディングウィンドウの考え方を用いることで、各 \)S_i\( を \)O(1)$ で更新しながら進めることができます。
アルゴリズム
- 各 \(i\) について、必要量 \(D_i = B_i - A_i\) を計算します。 \(D_i < 0\) となるものがあれば
-1を出力します。 - 各区画を左端とする操作回数を記録する配列 \(x\)(初期値はすべて \(0\))と、現在の区画にかかっている加算の合計
current_sumを用意します。 - \(i = 0\) から \(N-1\) まで順に以下を行います。
current_sumに \(x[i-1]\) を足し、範囲外となった \(x[i-K]\) を引くことで、現在の区画 \(i\) に適用されている加算の合計を \(O(1)\) で求めます。- \(i \le N - K\) の場合:
- \(x[i] = D_i - \text{current\_sum}\) とします。
- \(x[i] < 0\) なら
-1を出力して終了します。 - 合計操作回数
total_opsに \(x[i]\) を加算します。
- \(i > N - K\) の場合:
- \(D_i \neq \text{current\_sum}\) なら、目標値に一致させられないため
-1を出力して終了します。
- \(D_i \neq \text{current\_sum}\) なら、目標値に一致させられないため
- すべての区画を矛盾なく処理できれば、
total_opsが求める最小の操作回数となります。
計算量
- 時間計算量: \(O(N)\) 各区画 \(i\) に対する処理は、スライディングウィンドウによる定数回の加減算のみであるため、全体で \(O(N)\) の時間で動作します。
- 空間計算量: \(O(N)\) 入力の配列 \(A, B\)、および差分配列 \(D\)、操作回数配列 \(x\) を保持するために \(O(N)\) のメモリを使用します。
実装のポイント
オーバーフローの防止: 制約より、操作回数の合計は最大で \(3 \times 10^{14}\) に達することが保証されています。これは通常の 32 ビット整数型(C++の
intなど)の最大値(約 \(2 \times 10^9\))を超えるため、操作回数や水分量の計算には必ず 64 ビット整数型(C++のlong long)を使用してください。インデックスの境界条件:
current_sumの更新において、 \(i - 1 < 0\) や \(i - K < 0\) となる場合は、配列の範囲外アクセスを防ぐために条件分岐(if (i >= 1)やif (i >= K))を適切に行う必要があります。ソースコード
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
int main() {
// Optimize standard I/O operations for competitive programming
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N, K;
if (!(cin >> N >> K)) return 0;
vector<long long> A(N);
for (int i = 0; i < N; ++i) {
cin >> A[i];
}
vector<long long> B(N);
for (int i = 0; i < N; ++i) {
cin >> B[i];
}
vector<long long> D(N);
for (int i = 0; i < N; ++i) {
D[i] = B[i] - A[i];
if (D[i] < 0) {
cout << -1 << "\n";
return 0;
}
}
vector<long long> x(N, 0);
long long current_sum = 0;
long long total_ops = 0;
for (int i = 0; i < N; ++i) {
if (i >= 1) {
current_sum += x[i - 1];
}
if (i >= K) {
current_sum -= x[i - K];
}
if (i <= N - K) {
long long val = D[i] - current_sum;
if (val < 0) {
cout << -1 << "\n";
return 0;
}
x[i] = val;
total_ops += val;
} else {
if (D[i] != current_sum) {
cout << -1 << "\n";
return 0;
}
}
}
cout << total_ops << "\n";
return 0;
}
この解説は gemini-3.5-flash-thinking によって生成されました。
posted:
last update: