E - ダンジョン探索 / Dungeon Exploration Editorial by admin
Claude 4.6 Opus (Thinking)Overview
This is a problem about dungeon exploration where you clear rooms arranged in a line one by one, and you need to efficiently handle room information updates and exploration queries. We solve it efficiently by using a segment tree to manage the minimum combat power required to clear each interval.
Analysis
Issues with the Naive Approach
If we simulate from room \(1\) sequentially for each query 2, it takes \(O(N)\) in the worst case, and combined with the number of queries, this results in \(O(NQ)\) which causes TLE.
Key Insight
Whether you can clear all rooms in an interval \([l, r]\) can be determined solely by the combat power \(s\) at the time of entry. Specifically, if the combat power before entering the interval is large enough, all rooms in that interval can be cleared.
We define \(\mathrm{min\_need}(l, r)\) as the minimum initial combat power required to clear the interval \([l, r]\).
Merging Intervals
To compute \(\mathrm{min\_need}\) for the entire interval \([l, r]\) from the left half \([l, m]\) and the right half \([m+1, r]\):
- To clear the left half: initial combat power \(\geq \mathrm{min\_need}(l, m)\)
- To clear the right half: the combat power after clearing the left half must be \(\geq \mathrm{min\_need}(m+1, r)\). Since the combat power increases by a total of \(\mathrm{sum\_b}(l, m)\) in the left half, the initial combat power must be \(\geq \mathrm{min\_need}(m+1, r) - \mathrm{sum\_b}(l, m)\)
Therefore:
\[\mathrm{min\_need}(l, r) = \max\bigl(\mathrm{min\_need}(l, m),\; \mathrm{min\_need}(m+1, r) - \mathrm{sum\_b}(l, m)\bigr)\]
Algorithm
Build a segment tree where each node stores the following:
min_need: the minimum initial combat power required to clear all rooms in the intervalsum_b: the total combat power increase from defeating all monsters in the interval
Update (Query 1): Update the corresponding leaf and recompute its ancestors. \(O(\log N)\).
Exploration (Query 2): Descend on the segment tree as follows:
- If the entire interval of the current node can be cleared (\(s \geq \mathrm{min\_need}\)), add
sum_bto \(s\) and return the length of the interval. - Otherwise, first recurse into the left child.
- Only if the left child is fully cleared, also recurse into the right child.
- If the exploration fails partway through the left child, return the count up to that point.
This descent allows us to determine “how far we can progress” in \(O(\log N)\).
Concrete Example
If the rooms have \([A_1=3, A_2=5, A_3=8]\) and \([B_1=2, B_2=1, B_3=4]\):
- Starting with combat power \(s=4\): room 1 can be cleared (\(4 \geq 3\), \(s=6\)), room 2 can also be cleared (\(6 \geq 5\), \(s=7\)), room 3 cannot be cleared (\(7 < 8\)) → the answer is 2.
- The
min_needat the root of the segment tree is \(\max(3, \max(5, 8-1) - 2) = \max(3, 5) = 5\), so if \(s \geq 5\), all rooms are cleared.
Complexity
- Time complexity: \(O(N + Q \log N)\) (construction \(O(N)\), each query \(O(\log N)\))
- Space complexity: \(O(N)\) (size of the segment tree)
Implementation Notes
Since subtraction in the
min_needcalculation can become negative, uselong longtype.During query descent, update the combat power \(s\) by passing it by reference, so that the \(s\) after clearing the left child can be directly used for the right child’s evaluation.
At leaf nodes, store the information of a single room directly:
min_need = A[i],sum_b = B[i].Source Code
#include <bits/stdc++.h>
using namespace std;
struct Node {
long long min_need;
long long sum_b;
};
const int MAXN = 200005;
Node tree[4 * MAXN];
int A[MAXN], B[MAXN];
int N, Q;
void build(int v, int l, int r) {
if (l == r) {
tree[v].min_need = A[l];
tree[v].sum_b = B[l];
return;
}
int m = (l + r) / 2;
build(2*v, l, m);
build(2*v+1, m+1, r);
tree[v].sum_b = tree[2*v].sum_b + tree[2*v+1].sum_b;
tree[v].min_need = max(tree[2*v].min_need, tree[2*v+1].min_need - tree[2*v].sum_b);
}
void update(int v, int l, int r, int pos) {
if (l == r) {
tree[v].min_need = A[l];
tree[v].sum_b = B[l];
return;
}
int m = (l + r) / 2;
if (pos <= m) update(2*v, l, m, pos);
else update(2*v+1, m+1, r, pos);
tree[v].sum_b = tree[2*v].sum_b + tree[2*v+1].sum_b;
tree[v].min_need = max(tree[2*v].min_need, tree[2*v+1].min_need - tree[2*v].sum_b);
}
int query(int v, int l, int r, long long &s) {
if (l == r) {
if (s >= tree[v].min_need) {
s += tree[v].sum_b;
return 1;
}
return 0;
}
if (s >= tree[v].min_need) {
s += tree[v].sum_b;
return r - l + 1;
}
int m = (l + r) / 2;
int left_count = query(2*v, l, m, s);
if (left_count < m - l + 1) {
return left_count;
}
return left_count + query(2*v+1, m+1, r, s);
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> N >> Q;
for (int i = 1; i <= N; i++) {
cin >> A[i] >> B[i];
}
build(1, 1, N);
for (int q = 0; q < Q; q++) {
int type;
cin >> type;
if (type == 1) {
int i, a, b;
cin >> i >> a >> b;
A[i] = a;
B[i] = b;
update(1, 1, N, i);
} else {
long long s;
cin >> s;
cout << query(1, 1, N, s) << "\n";
}
}
return 0;
}
This editorial was generated by claude4.6opus-thinking.
posted:
last update: