E - 目標達成トレーニング / Goal Achievement Training 解説 by admin
Claude 4.6 Opus (Thinking)概要
指定された区間 \([L, R]\) 内で、連続部分列の和が \(P - D\) に等しくなるような組 \((l, r)\) の個数とその日数の総和を、オフラインクエリ処理とBIT(Binary Indexed Tree)を用いて効率的に求める問題です。
考察
重要な気づき
条件 \(D + A_l + A_{l+1} + \cdots + A_r = P\) は、累積和 \(S[i] = A_1 + \cdots + A_i\)(\(S[0] = 0\))を定義すると、次のように書き換えられます:
\[S[r] - S[l-1] = P - D \quad (\text{target} = P - D)\]
\(A_i \geq 1\) なので累積和 \(S\) は狭義単調増加です。これにより、各 \(l\) に対して条件を満たす \(r\) は高々1つしか存在しません。二分探索で見つけることができます。
素朴なアプローチの問題点
各クエリに対して区間 \([L, R]\) 内の全ての \((l, r)\) ペアを探索すると、最悪 \(O(N)\) かかり、\(Q\) 個のクエリに対して合計 \(O(NQ)\) となりTLEします。
解決方針
- あらかじめ全ての有効なペア \((l, r)\) を前計算する
- クエリをオフラインで \(R\) の昇順にソートし、スイープラインで処理する
- BITを用いて、区間内のペア数と日数総和を高速に求める
アルゴリズム
前計算: 各 \(l = 1, \ldots, N\) に対し、\(S[r] = S[l-1] + \text{target}\) を満たす \(r\) を二分探索で求め、見つかれば
by_r[r]に \(l\) を登録する。クエリのオフライン処理: クエリを \(R\) の昇順にソートする。
スイープライン + BIT: 変数
cur_rを左から右に進めながら:cur_rに到達したとき、by_r[cur_r]に含まれる全ての \(l\) について:cnt_bitの位置 \(l\) に \(+1\)(ペア数の管理)sum_bitの位置 \(l\) に \(+(r - l + 1)\)(日数の管理)
- クエリ \([L, R]\) に対して、
cnt_bit.query(L, R)が \(C_i\)、sum_bit.query(L, R)が \(T_i\)
ポイント: BITは \(l\) でインデックス付けされています。cur_r が \(q.R\) まで進んだ時点でBITに登録されているのは「\(r \leq q.R\) を満たすペア」です。クエリ \([L, R]\) で \(l \geq L\) の範囲を問い合わせれば、\(L \leq l\) かつ \(r \leq R\)(さらに前計算時に \(l \leq r\) は保証済み)を満たすペアだけが数えられます。
計算量
- 時間計算量: \(O(N \log N + Q \log N)\)
- 前計算の二分探索: \(O(N \log N)\)
- スイープ+BIT更新: 有効ペアは最大 \(N\) 個で各 \(O(\log N)\)
- クエリ応答: 各 \(O(\log N)\)
- 空間計算量: \(O(N + Q)\)
実装のポイント
\(A_i \geq 1\) により累積和が狭義単調増加なので、各 \(l\) に対して条件を満たす \(r\) は高々1つ。これが問題を大幅に簡単にしている。
target = P - Dが \(0\) の場合でも、\(A_i \geq 1\) なので \(S[r] - S[l-1] = 0\) を満たす \(l \leq r\) は存在せず問題ない。クエリを \(R\) でソートしてスイープすることで、「\(r \leq R\) のペアのみがBITに存在する」状態を保つのが鍵。
BITの区間クエリ
query(L, R) = query(R) - query(L-1)で \(l \geq L\) の条件を処理する。ソースコード
#include <bits/stdc++.h>
using namespace std;
struct BIT {
int n;
vector<long long> tree;
BIT(int n) : n(n), tree(n+1, 0) {}
void update(int i, long long val) {
for (; i <= n; i += i & (-i)) tree[i] += val;
}
long long query(int i) {
long long s = 0;
for (; i > 0; i -= i & (-i)) s += tree[i];
return s;
}
long long query(int l, int r) {
return query(r) - query(l-1);
}
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N, Q;
long long P, D;
cin >> N >> Q >> P >> D;
long long target = P - D;
vector<long long> S(N+1, 0);
for (int i = 1; i <= N; i++) {
int a; cin >> a;
S[i] = S[i-1] + a;
}
// For each l, find r_of[l] such that S[r] - S[l-1] = target
vector<vector<int>> by_r(N+2);
for (int l = 1; l <= N; l++) {
long long need = S[l-1] + target;
if (need < S[l] || need > S[N]) continue;
// Binary search for need in S[l..N]
int lo = l, hi = N, found = -1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (S[mid] == need) { found = mid; break; }
else if (S[mid] < need) lo = mid + 1;
else hi = mid - 1;
}
if (found != -1) {
by_r[found].push_back(l);
}
}
// Process queries offline sorted by R
struct Query { int L, R, idx; };
vector<Query> queries(Q);
for (int i = 0; i < Q; i++) {
cin >> queries[i].L >> queries[i].R;
queries[i].idx = i;
}
sort(queries.begin(), queries.end(), [](const Query& a, const Query& b) {
return a.R < b.R;
});
BIT cnt_bit(N), sum_bit(N);
vector<long long> ans_c(Q), ans_t(Q);
int cur_r = 0;
for (auto& q : queries) {
while (cur_r < q.R) {
cur_r++;
for (int l : by_r[cur_r]) {
cnt_bit.update(l, 1);
sum_bit.update(l, (long long)(cur_r - l + 1));
}
}
ans_c[q.idx] = cnt_bit.query(q.L, q.R);
ans_t[q.idx] = sum_bit.query(q.L, q.R);
}
for (int i = 0; i < Q; i++) {
cout << ans_c[i] << " " << ans_t[i] << "\n";
}
return 0;
}
この解説は claude4.6opus-thinking によって生成されました。
投稿日時:
最終更新: