E - フェンスの塗装 / Painting the Fence Editorial by admin Editorial for printing
Claude 4.6 Opus (Thinking)概要
\(M\) 個の区間のうち連続する \(K\) 個をスキップしたときの塗装板数を最大化する問題。全区間の和集合から「スキップによる損失」を最小化する方針で解く。
考察
基本方針:全体から損失を引く
全 \(M\) 個の指示を実行した場合の塗装枚数(全区間の和集合の大きさ)を totalUnion とする。スキップ開始位置 \(s\) を選んだときの「損失」を \(\text{loss}(s)\) とすると、答えは:
\[\max_{s} (\text{totalUnion} - \text{loss}(s)) = \text{totalUnion} - \min_{s} \text{loss}(s)\]
損失の意味
あるセグメント(板の連続区間)が「損失」になるのは、そのセグメントを塗る指示がすべてスキップ範囲 \([s, s+K-1]\) に含まれる場合のみ。
セグメントごとの分析
座標圧縮後の各セグメントについて、そのセグメントをカバーする指示の中で: - 最小インデックス を \(\text{min\_idx}\) - 最大インデックス を \(\text{max\_idx}\)
とすると、このセグメントが損失になる条件は: $\(s \leq \text{min\_idx} \quad \text{かつ} \quad s + K - 1 \geq \text{max\_idx}\)$
整理すると、\(s\) の範囲は \([\text{max\_idx} - K + 1,\ \text{min\_idx}]\) となる。
素朴解の問題
各 \(s\) に対して愚直に損失を計算すると \(O(M \cdot N)\) で TLE。しかし差分配列を使えば、各セグメントの寄与を \(O(1)\) で記録し、最後にまとめて計算できる。
アルゴリズム
座標圧縮: 各区間 \([L_i, R_i]\) を半開区間 \([L_i, R_i+1)\) として、境界値を集め、ソート・重複除去する。これにより最大 \(2M\) 個のセグメントに分割。
スイープライン: 座標圧縮されたセグメントを左から順に走査。各セグメントにおいてアクティブな指示集合(そのセグメントをカバーする指示のインデックス集合)を
multisetで管理。差分配列で損失を記録: 各セグメントについて、アクティブ集合の最小・最大インデックスから、損失が発生する \(s\) の範囲 \([lo, hi]\) を計算し、差分配列に長さを加算。
最小損失の計算: 差分配列を累積和して各 \(s\) に対する \(\text{loss}(s)\) を復元し、最小値を求める。
答え: \(\text{totalUnion} - \min_s \text{loss}(s)\)
計算量
- 時間計算量: \(O(M \log M)\)(座標圧縮のソート、multiset操作が各 \(O(\log M)\) で計 \(O(M)\) 回)
- 空間計算量: \(O(M)\)(座標、イベント、差分配列の格納)
実装のポイント
座標圧縮: \(N\) が最大 \(10^9\) なので必須。半開区間 \([L_i, R_i+1)\) を使うと扱いやすい。
イベントのソート: 同じ座標で終了イベント(type=0)が開始イベント(type=1)より先に処理されるよう、
(座標, type, index)のタプルでソート。1-indexed と 0-indexed の変換: コード内ではインデックスを 0-indexed で管理し、差分配列の計算時に 1-indexed に変換。
\(lo > hi\) の場合: そのセグメントはどの \(s\) を選んでも損失にならない(スキップ範囲 \(K\) 個では全指示を含みきれない)。この場合はスキップ。
long longの使用: \(N\) が大きいため、塗装枚数がオーバーフローしないよう注意。ソースコード
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
long long N;
int M, K;
cin >> N >> M >> K;
vector<int> L(M), R(M);
for (int i = 0; i < M; i++) {
cin >> L[i] >> R[i];
}
// Collect coordinate boundaries (half-open intervals [L_i, R_i+1))
vector<int> coords;
for (int i = 0; i < M; i++) {
coords.push_back(L[i]);
coords.push_back(R[i] + 1);
}
sort(coords.begin(), coords.end());
coords.erase(unique(coords.begin(), coords.end()), coords.end());
// Events: (position, type, index) - type 0 = end, type 1 = start
vector<tuple<int, int, int>> evts;
for (int i = 0; i < M; i++) {
evts.push_back({L[i], 1, i});
evts.push_back({R[i] + 1, 0, i});
}
sort(evts.begin(), evts.end());
multiset<int> active;
long long totalUnion = 0;
int evt_idx = 0;
// Difference array for loss(s), s from 1 to M-K+1
vector<long long> diff(M + 3, 0);
for (int c = 0; c + 1 < (int)coords.size(); c++) {
int left = coords[c];
int right = coords[c + 1];
long long len = (long long)(right - left);
// Process all events at position 'left'
// End events (type 0) come before start events (type 1) due to sorting
while (evt_idx < (int)evts.size() && get<0>(evts[evt_idx]) == left) {
auto [pos, type, idx] = evts[evt_idx];
if (type == 1) active.insert(idx);
else active.erase(active.find(idx));
evt_idx++;
}
if (!active.empty()) {
totalUnion += len;
int min_idx = *active.begin() + 1; // 1-indexed
int max_idx = *active.rbegin() + 1; // 1-indexed
// This segment is lost when s in [max_idx - K + 1, min_idx]
// intersected with [1, M - K + 1]
int lo = max(1, max_idx - K + 1);
int hi = min(M - K + 1, min_idx);
if (lo <= hi) {
diff[lo] += len;
diff[hi + 1] -= len;
}
}
}
// Find minimum loss
long long minLoss = LLONG_MAX;
long long current = 0;
for (int s = 1; s <= M - K + 1; s++) {
current += diff[s];
minLoss = min(minLoss, current);
}
cout << totalUnion - minLoss << endl;
return 0;
}
この解説は claude4.6opus-thinking によって生成されました。
posted:
last update: