D - 分岐する棚の整理 / Organizing Branching Shelves 解説 by admin
Claude 4.6 Opus (Thinking)概要
木構造を成す状態遷移において、各状態での棚から落下した置物の個数を求める問題。累積オフセットの最小値・最大値を管理し、二分探索で生存する置物を数える。
考察
重要な気づき:置物が生き残る条件
置物 \(i\) の初期位置を \(A_i\) とする。状態 \(0\) から状態 \(j\) までのパス上の各状態 \(k\) における累積オフセット(全移動の合計)を \(\text{offset}[k]\) とすると、置物 \(i\) の状態 \(k\) での位置は \(A_i + \text{offset}[k]\) である。
置物が状態 \(j\) で生き残るためには、パス上のすべての状態で位置が \([1, M]\) の範囲内でなければならない。 途中で一度でも落下すれば、コピー時にその置物は含まれないからである。
この条件は次のように書き換えられる:
\[1 \leq A_i + \text{offset}[k] \leq M \quad (\text{パス上のすべての } k)\]
これは以下と同値:
\[A_i + \min_{k} \text{offset}[k] \geq 1 \quad \text{かつ} \quad A_i + \max_{k} \text{offset}[k] \leq M\]
つまり、生存条件は:
\[1 - \text{min\_off}[j] \leq A_i \leq M - \text{max\_off}[j]\]
素朴なアプローチの問題点
各状態ごとに全置物をシミュレーションすると \(O(NQ)\) で TLE になる。しかし上記の観察により、各状態で管理すべきは「累積オフセット」「パス上のオフセット最小値」「パス上のオフセット最大値」の3つだけでよい。
アルゴリズム
- 初期位置 \(A\) をソートする。
- 各状態 \(j\) について、親状態 \(P_j\) から以下を \(O(1)\) で計算する:
- \(\text{offset}[j] = \text{offset}[P_j] \pm D_j\)(方向による)
- \(\text{min\_off}[j] = \min(\text{min\_off}[P_j],\ \text{offset}[j])\)
- \(\text{max\_off}[j] = \max(\text{max\_off}[P_j],\ \text{offset}[j])\)
- 生存する置物は \(A_i \in [\text{lo}, \text{hi}]\)(\(\text{lo} = 1 - \text{min\_off}[j]\), \(\text{hi} = M - \text{max\_off}[j]\))を満たすもの。ソート済み配列に対して二分探索で個数を求める。
- 落下数は \(N - \text{(生存数)}\)。
具体例
\(N=3, M=10, A=[2, 5, 9]\) で、状態1が「状態0から右に3移動」、状態2が「状態1から左に6移動」の場合:
- 状態1: offset=3, min_off=0, max_off=3 → 生存範囲 \([1, 7]\) → \(A_i \in \{2,5\}\) が生存、落下1個
- 状態2: offset=-3, min_off=-3, max_off=3 → 生存範囲 \([4, 7]\) → \(A_i \in \{5\}\) が生存、落下2個
計算量
- 時間計算量: \(O(N \log N + Q \log N)\)(ソートに \(O(N \log N)\)、各クエリで二分探索に \(O(\log N)\))
- 空間計算量: \(O(N + Q)\)
実装のポイント
オフセットは \(10^9 \times 2 \times 10^5\) 程度まで大きくなりうるので
long longを使用する。\(\text{lo} > \text{hi}\) のとき(生存可能な範囲が空)は生存数を0とする。
状態0の offset, min_off, max_off はすべて0で初期化する。
各状態は親からの情報のみで計算できるため、木構造を明示的に構築する必要はなく、入力順に処理すれば十分(\(P_j < j\) が保証されているため)。
ソースコード
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
long long M;
int Q;
cin >> N >> M >> Q;
vector<long long> A(N);
for(int i = 0; i < N; i++) cin >> A[i];
sort(A.begin(), A.end());
// For each state, store: offset, min_offset along path, max_offset along path
// State 0: offset=0, min_off=0, max_off=0
vector<long long> offset(Q+1), min_off(Q+1), max_off(Q+1);
offset[0] = 0;
min_off[0] = 0;
max_off[0] = 0;
for(int j = 1; j <= Q; j++){
int P;
char C;
long long D;
cin >> P >> C >> D;
if(C == 'R'){
offset[j] = offset[P] + D;
} else {
offset[j] = offset[P] - D;
}
min_off[j] = min(min_off[P], offset[j]);
max_off[j] = max(max_off[P], offset[j]);
// Valid range for A_i: [1 - min_off[j], M - max_off[j]]
long long lo = 1 - min_off[j];
long long hi = M - max_off[j];
long long surviving = 0;
if(lo <= hi){
// count A_i in [lo, hi]
auto left = lower_bound(A.begin(), A.end(), lo);
auto right = upper_bound(A.begin(), A.end(), hi);
surviving = right - left;
}
cout << N - surviving << '\n';
}
return 0;
}
この解説は claude4.6opus-thinking によって生成されました。
投稿日時:
最終更新: