Dungeons
There are n dungeons (indexed 0 to n-1) and a final dungeon n. Each dungeon i has: s[i]: opponent strength p[i]: strength gain if you lose w[i]: next dungeon if you win (w[i] > i) l[i]: next dungeon if you lose If you...
Problem Statement
Rendered from the "Problem Summary" section in the LaTeX write-up.
There are $n$ dungeons (indexed $0$ to $n-1$) and a final dungeon $n$. Each dungeon $i$ has:
$s[i]$: opponent strength
$p[i]$: strength gain if you lose
$w[i]$: next dungeon if you win ($w[i] > i$)
$l[i]$: next dungeon if you lose
If you win (your strength $\ge s[i]$), gain $s[i]$ strength and go to $w[i]$. If you lose, gain $p[i]$ strength and go to $l[i]$.
Given starting dungeon and initial strength, simulate until reaching dungeon $n$. Return final strength.
Editorial
The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.
Solution Approach
Binary Lifting with Threshold Grouping
Direct simulation is $O(n)$ per query in the worst case. We use binary lifting with strength thresholds.
Key Idea
Group operations by strength thresholds. Define $T = \lceil \log_2(\max s[i]) \rceil$ threshold levels: $2^0, 2^1, \ldots, 2^T$.
For threshold level $k$ (threshold $= 2^k$): if the hero's strength $\ge 2^k$, they win against all opponents with $s[i] < 2^k$. Pre-compute ``fast forwarding'' through dungeons where all opponents have $s[i] < 2^k$.
Preprocessing
For each level $k$ and dungeon $i$, compute:
$\text{nxt}[k][i]$: the dungeon reached after following the path from $i$ until encountering an opponent with $s[i] \ge 2^k$ (or reaching dungeon $n$).
$\text{gain}[k][i]$: total strength gained along this path.
This is done using binary lifting: $\text{nxt}[k][i]$ and $\text{gain}[k][i]$ for $2^j$ steps.
Query Processing
For a query with starting dungeon $d$ and strength $z$:
Find the lowest level $k$ such that $z \ge 2^k$.
Fast-forward through all opponents with $s < 2^k$.
After gaining strength, $z$ may exceed $2^{k+1}$; move to the next level.
Repeat until reaching dungeon $n$.
C++ Solution
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 400005;
const int LEVELS = 25; // log2(max strength)
const int LOG = 25; // binary lifting depth
int n;
int s_val[MAXN], p_val[MAXN], w_val[MAXN], l_val[MAXN];
// For each level k, nxt[k][j][i] = destination after 2^j steps at level k
// gain[k][j][i] = strength gained after 2^j steps at level k
int nxt[LEVELS][LOG][MAXN];
ll gain[LEVELS][LOG][MAXN];
void init(int _n, vector<int> s, vector<int> p, vector<int> w, vector<int> l) {
n = _n;
for (int i = 0; i < n; i++) {
s_val[i] = s[i];
p_val[i] = p[i];
w_val[i] = w[i];
l_val[i] = l[i];
}
for (int k = 0; k < LEVELS; k++) {
ll threshold = 1LL << k;
// Base case (1 step at level k):
// If strength >= threshold and at dungeon i:
// If s[i] < threshold: we definitely win -> gain s[i], go to w[i]
// If s[i] >= threshold: we stop (can't fast-forward)
for (int i = 0; i <= n; i++) {
if (i == n) {
nxt[k][0][i] = n;
gain[k][0][i] = 0;
} else if (s_val[i] < threshold) {
// Win: gain s[i], go to w[i]
nxt[k][0][i] = w_val[i];
gain[k][0][i] = s_val[i];
} else {
// Can't fast-forward: stop here
nxt[k][0][i] = i;
gain[k][0][i] = 0;
}
}
// Binary lifting
for (int j = 1; j < LOG; j++) {
for (int i = 0; i <= n; i++) {
int mid = nxt[k][j-1][i];
nxt[k][j][i] = nxt[k][j-1][mid];
gain[k][j][i] = gain[k][j-1][i] + gain[k][j-1][mid];
}
}
}
}
ll simulate(int x, int z) {
ll strength = z;
int dungeon = x;
while (dungeon != n) {
// Find appropriate level
int k = 0;
while (k < LEVELS - 1 && (1LL << (k + 1)) <= strength) k++;
// strength >= 2^k but < 2^(k+1) (or k = LEVELS-1)
// Fast-forward through opponents with s < 2^k
// But at level k, we can only skip opponents with s < 2^k.
// We want the highest level where we can skip.
if (k >= LEVELS) k = LEVELS - 1;
// Try to fast-forward at level k
bool moved = false;
for (int j = LOG - 1; j >= 0; j--) {
int nd = nxt[k][j][dungeon];
ll ng = gain[k][j][dungeon];
if (nd != dungeon) {
// Check we don't overshoot (strength stays < 2^(k+1) to stay in level k)
// Actually, gaining strength only helps. Just apply.
if (strength + ng < (1LL << (k + 1)) || k == LEVELS - 1) {
strength += ng;
dungeon = nd;
moved = true;
}
}
}
if (!moved) {
// We're at an opponent with s[dungeon] >= 2^k
// or strength has grown past 2^(k+1)
if (dungeon == n) break;
if (strength >= s_val[dungeon]) {
// Win
strength += s_val[dungeon];
dungeon = w_val[dungeon];
} else {
// Lose
strength += p_val[dungeon];
dungeon = l_val[dungeon];
}
}
}
return strength;
}
int main() {
int _n, q;
scanf("%d %d", &_n, &q);
vector<int> s(_n), p(_n), w(_n), l(_n);
for (int i = 0; i < _n; i++)
scanf("%d %d %d %d", &s[i], &p[i], &w[i], &l[i]);
init(_n, s, p, w, l);
while (q--) {
int x, z;
scanf("%d %d", &x, &z);
printf("%lld\n", simulate(x, z));
}
return 0;
}
Complexity Analysis
Preprocessing: $O(n \cdot L \cdot \log n)$ where $L = O(\log(\max s))$ is the number of threshold levels.
Per query: $O(L \cdot \log n)$ -- we pass through at most $L$ levels (strength can at most double $L$ times), and at each level, the binary lifting takes $O(\log n)$.
Space: $O(n \cdot L \cdot \log n)$.
Code
C++ solution used for this page.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// IOI 2021 - Dungeons
// Binary lifting with strength threshold levels.
// At level k (threshold 2^k), precompute fast-forward through all opponents
// with s[i] < 2^k (guaranteed win). Use binary lifting for O(log n) per step.
const int MAXN = 400005;
const int LEVELS = 25;
const int LOG = 25;
int n;
int s_val[MAXN], p_val[MAXN], w_val[MAXN], l_val[MAXN];
// nxt[k][j][i] = destination after 2^j win-steps at level k
// gain[k][j][i] = total strength gained in those steps
int nxt[LEVELS][LOG][MAXN];
ll gain[LEVELS][LOG][MAXN];
void init(int _n, vector<int> s, vector<int> p, vector<int> w, vector<int> l) {
n = _n;
for (int i = 0; i < n; i++) {
s_val[i] = s[i];
p_val[i] = p[i];
w_val[i] = w[i];
l_val[i] = l[i];
}
for (int k = 0; k < LEVELS; k++) {
ll threshold = 1LL << k;
// Base case: one step at level k
for (int i = 0; i <= n; i++) {
if (i == n) {
nxt[k][0][i] = n;
gain[k][0][i] = 0;
} else if (s_val[i] < threshold) {
// Guaranteed win: gain s[i], go to w[i]
nxt[k][0][i] = w_val[i];
gain[k][0][i] = s_val[i];
} else {
// Cannot fast-forward: stop
nxt[k][0][i] = i;
gain[k][0][i] = 0;
}
}
// Binary lifting
for (int j = 1; j < LOG; j++) {
for (int i = 0; i <= n; i++) {
int mid = nxt[k][j - 1][i];
nxt[k][j][i] = nxt[k][j - 1][mid];
gain[k][j][i] = gain[k][j - 1][i] + gain[k][j - 1][mid];
}
}
}
}
ll simulate(int x, int z) {
ll strength = z;
int dungeon = x;
while (dungeon != n) {
// Find highest level where we can fast-forward
int k = 0;
while (k < LEVELS - 1 && (1LL << (k + 1)) <= strength) k++;
// Fast-forward through winnable opponents at this level
bool moved = false;
for (int j = LOG - 1; j >= 0; j--) {
int nd = nxt[k][j][dungeon];
ll ng = gain[k][j][dungeon];
if (nd != dungeon) {
if (strength + ng < (1LL << (k + 1)) || k == LEVELS - 1) {
strength += ng;
dungeon = nd;
moved = true;
}
}
}
if (!moved) {
if (dungeon == n) break;
// Handle single encounter
if (strength >= s_val[dungeon]) {
strength += s_val[dungeon];
dungeon = w_val[dungeon];
} else {
strength += p_val[dungeon];
dungeon = l_val[dungeon];
}
}
}
return strength;
}
int main() {
int _n, q;
scanf("%d %d", &_n, &q);
vector<int> s(_n), p(_n), w(_n), l(_n);
for (int i = 0; i < _n; i++)
scanf("%d %d %d %d", &s[i], &p[i], &w[i], &l[i]);
init(_n, s, p, w, l);
while (q--) {
int x, z;
scanf("%d %d", &x, &z);
printf("%lld\n", simulate(x, z));
}
return 0;
}
Source Files and Assets
Raw files are still available here when you want the original TeX, C++, or statement assets.