E - 目標達成トレーニング / Goal Achievement Training 解説 by admin
gpt-5.5-xhigh概要
各クエリ \([L_i, R_i]\) について、区間和が \(P-D\) になる連続部分列 \((l,r)\) のうち、\(L_i \leq l \leq r \leq R_i\) を満たすものの個数 \(C_i\) と、長さ \(r-l+1\) の総和 \(T_i\) を求めます。
考察
条件
\(D + A_l + A_{l+1} + \cdots + A_r = P\)
は、言い換えると
\(A_l + A_{l+1} + \cdots + A_r = P - D\)
です。
ここで \(A_i \geq 1\) なので、累積和は単調増加します。
累積和を
\(S_i = A_1 + A_2 + \cdots + A_i\)
とすると、区間 \([l,r]\) の和は
\(S_r - S_{l-1}\)
です。
したがって、求めたい区間は
\(S_r = S_{l-1} + (P-D)\)
を満たすものです。
\(A_i\) がすべて正なので、固定した \(l\) に対して条件を満たす \(r\) は高々 \(1\) つです。
そのため、各 \(l\) について二分探索を行えば、条件を満たす区間をすべて列挙できます。
素朴に各クエリごとにすべての部分区間を調べると、最悪で \(O(QN^2)\) となり間に合いません。
また、あらかじめ条件を満たす区間を列挙できたとしても、各クエリごとにそれらをすべて調べると \(O(NQ)\) になり、これも間に合いません。
そこで、条件を満たす区間 \((l,r)\) を先にすべて列挙し、クエリ
\(L \leq l,\ r \leq R\)
を高速に処理します。
これは「右端 \(r\) がクエリの \(R\) 以下の区間だけを追加しておき、左端 \(l\) が \(L\) 以上のものを数える」という形で処理できます。
そのために、区間を右端 \(r\) の昇順にソートし、クエリも \(R\) の昇順にソートして、Fenwick Tree(BIT)で管理します。
アルゴリズム
まず、目標となる区間和を
\(target = P - D\)
とします。
ただし、\(target = 0\) の場合は注意が必要です。
この問題では \(A_i \geq 1\) かつ \(l \leq r\) なので、空でない区間の和は必ず正です。
したがって、\(target = 0\) のとき条件を満たす区間は存在しません。
1. 条件を満たす区間を列挙する
累積和配列 \(S\) を用意します。
各左端 \(l\) について、
\(S_r = S_{l-1} + target\)
となる \(r\) を二分探索します。
見つかった場合、その区間 \((l,r)\) を保存します。
また、その区間の長さ
\(r-l+1\)
も保存しておきます。
例えば、条件を満たす区間を
\((l,r,w)\)
として保存します。ここで \(w = r-l+1\) です。
2. 区間を右端 \(r\) でソートする
列挙した区間を、右端 \(r\) の昇順に並べます。
クエリ \([L,R]\) に対して必要なのは、
- \(r \leq R\)
- \(l \geq L\)
を満たす区間です。
そこで、右端 \(r\) が小さい順に区間を追加していきます。
3. クエリも \(R\) の昇順に処理する
クエリも \(R\) の昇順にソートします。
現在処理しているクエリを \([L,R]\) とします。
右端 \(r \leq R\) である区間をすべて Fenwick Tree に追加します。
Fenwick Tree には、左端 \(l\) の位置に対して以下を追加します。
- 個数用 BIT に \(1\)
- 長さ合計用 BIT に \(w\)
すると、現在 BIT に入っている区間はすべて \(r \leq R\) を満たしています。
あとは、その中から \(l \geq L\) のものを数えればよいです。
これは BIT の区間和で
\([L,N]\)
を取得すれば求められます。
個数用 BIT から \(C_i\)、長さ合計用 BIT から \(T_i\) が得られます。
具体例として、条件を満たす区間が
\((1,2)\), \((3,3)\), \((4,5)\)
だったとします。
クエリが \([2,4]\) の場合、必要なのは
\(2 \leq l \leq r \leq 4\)
を満たす区間です。
右端が \(4\) 以下の区間だけを BIT に入れると、
\((1,2)\), \((3,3)\)
が入ります。
そのうち左端が \(2\) 以上なのは
\((3,3)\)
だけなので、答えは個数 \(1\)、長さ合計 \(1\) になります。
計算量
- 時間計算量: \(O((N+Q)\log N)\)
- 空間計算量: \(O(N+Q)\)
区間列挙で各 \(l\) について二分探索を行うため \(O(N\log N)\)、区間とクエリのソートおよび Fenwick Tree の操作で \(O((N+Q)\log N)\) です。
実装のポイント
\(P-D\) を
targetとして扱います。\(target=0\) の場合、\(A_i \geq 1\) なので条件を満たす非空区間は存在しません。
累積和は最大で \(2 \times 10^5 \times 10^4 = 2 \times 10^9\) 程度ですが、答えの長さ合計は大きくなる可能性があるため、
long longを使います。Fenwick Tree は以下の 2 本を用意します。
- 区間の個数を管理する BIT
- 区間の長さ合計を管理する BIT
クエリは元の順番で答えを出力する必要があるため、クエリに添字
idxを持たせておきます。ソースコード
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Fenwick {
int n;
vector<ll> bit;
Fenwick(int n = 0) : n(n), bit(n + 1, 0) {}
void add(int idx, ll val) {
for (; idx <= n; idx += idx & -idx) bit[idx] += val;
}
ll sum(int idx) const {
ll res = 0;
for (; idx > 0; idx -= idx & -idx) res += bit[idx];
return res;
}
ll range_sum(int l, int r) const {
if (l > r) return 0;
return sum(r) - sum(l - 1);
}
};
struct Interval {
int l, r;
ll w;
};
struct Query {
int l, r, idx;
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, Q;
ll P, D;
cin >> N >> Q >> P >> D;
vector<ll> A(N + 1), pref(N + 1, 0);
for (int i = 1; i <= N; i++) {
cin >> A[i];
pref[i] = pref[i - 1] + A[i];
}
ll target = P - D;
vector<Interval> intervals;
if (target > 0) {
for (int l = 1; l <= N; l++) {
ll need = pref[l - 1] + target;
auto it = lower_bound(pref.begin() + l, pref.end(), need);
if (it != pref.end() && *it == need) {
int r = int(it - pref.begin());
intervals.push_back({l, r, r - l + 1});
}
}
}
sort(intervals.begin(), intervals.end(), [](const Interval& a, const Interval& b) {
if (a.r != b.r) return a.r < b.r;
return a.l < b.l;
});
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;
});
Fenwick bitCnt(N), bitLen(N);
vector<ll> ansC(Q, 0), ansT(Q, 0);
int ptr = 0;
for (const auto& q : queries) {
while (ptr < (int)intervals.size() && intervals[ptr].r <= q.r) {
bitCnt.add(intervals[ptr].l, 1);
bitLen.add(intervals[ptr].l, intervals[ptr].w);
ptr++;
}
ansC[q.idx] = bitCnt.range_sum(q.l, N);
ansT[q.idx] = bitLen.range_sum(q.l, N);
}
for (int i = 0; i < Q; i++) {
cout << ansC[i] << ' ' << ansT[i] << '\n';
}
return 0;
}
この解説は gpt-5.5-xhigh によって生成されました。
投稿日時:
最終更新: