← Назад к структурам данных

Лунки и прыжки (sqrt-декомпозиция)

Эффективная обработка прыжков по массиву с обновлениями

🐍 Python
n, m = map(int, input().split())
A = list(map(int, input().split()))
dp = [[-1] * 2 for _ in range(n)]
c = int(n ** 0.5) + 1

for i in range(n - 1, -1, -1):
    if i + A[i] >= n:
        dp[i][0] = 1
        dp[i][1] = -1
    else:
        if (i + A[i]) // c > i // c:
            dp[i][0] = 1
            dp[i][1] = i + A[i]
        else:
            dp[i][0] = dp[i + A[i]][0] + 1
            if dp[i + A[i]][1] == -1:
                dp[i][1] = i + A[i]
                dp[i][0] = 1
            else:
                dp[i][1] = dp[i + A[i]][1]

for _ in range(m):
    s = list(map(int, input().split()))
    if s[0] == 0:
        j = s[1] - 1
        A[j] = s[2]
        for i in range(min((j // c + 1) * c - 1, n - 1), j // c * c - 1, -1):
            if i + A[i] >= n:
                dp[i][0] = 1
                dp[i][1] = -1
            else:
                if (i + A[i]) // c > i // c:
                    dp[i][0] = 1
                    dp[i][1] = i + A[i]
                else:
                    dp[i][0] = dp[i + A[i]][0] + 1
                    if dp[i + A[i]][1] == -1:
                        dp[i][1] = i + A[i]
                        dp[i][0] = 1
                    else:
                        dp[i][1] = dp[i + A[i]][1]
    else:
        i = s[1] - 1
        z = 0
        while dp[i][1] != -1:
            z += dp[i][0]
            i = dp[i][1]
        print(i + 1, z + 1)
⚙️ C++
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int n, m;
    cin >> n >> m;
    
    vector<int> A(n);
    for (int i = 0; i < n; i++) {
        cin >> A[i];
    }
    
    int c = sqrt(n) + 1;
    vector<vector<int>> dp(n, vector<int>(2, -1));
    
    for (int i = n - 1; i >= 0; i--) {
        if (i + A[i] >= n) {
            dp[i][0] = 1;
            dp[i][1] = -1;
        } else {
            if ((i + A[i]) / c > i / c) {
                dp[i][0] = 1;
                dp[i][1] = i + A[i];
            } else {
                dp[i][0] = dp[i + A[i]][0] + 1;
                if (dp[i + A[i]][1] == -1) {
                    dp[i][1] = i + A[i];
                    dp[i][0] = 1;
                } else {
                    dp[i][1] = dp[i + A[i]][1];
                }
            }
        }
    }
    
    for (int q = 0; q < m; q++) {
        int type;
        cin >> type;
        
        if (type == 0) {
            int j, x;
            cin >> j >> x;
            j--;
            A[j] = x;
            
            int block_start = (j / c) * c;
            int block_end = min((j / c + 1) * c - 1, n - 1);
            
            for (int i = block_end; i >= block_start; i--) {
                if (i + A[i] >= n) {
                    dp[i][0] = 1;
                    dp[i][1] = -1;
                } else {
                    if ((i + A[i]) / c > i / c) {
                        dp[i][0] = 1;
                        dp[i][1] = i + A[i];
                    } else {
                        dp[i][0] = dp[i + A[i]][0] + 1;
                        if (dp[i + A[i]][1] == -1) {
                            dp[i][1] = i + A[i];
                            dp[i][0] = 1;
                        } else {
                            dp[i][1] = dp[i + A[i]][1];
                        }
                    }
                }
            }
        } else {
            int i;
            cin >> i;
            i--;
            int steps = 0;
            while (dp[i][1] != -1) {
                steps += dp[i][0];
                i = dp[i][1];
            }
            cout << i + 1 << " " << steps + 1 << "\n";
        }
    }
    
    return 0;
}

Как работает sqrt-декомпозиция для прыжков?

Задача: есть массив A. Из позиции i можно прыгнуть на A[i] позиций вперёд. Нужно отвечать на запросы: найти последнюю позицию и количество прыжков, а также обновлять A[i].

Идея: Разбиваем массив на блоки размером ≈√n. Для каждой позиции предвычисляем:
dp[i][0] — сколько прыжков нужно, чтобы выйти из текущего блока
dp[i][1] — в какую позицию мы попадём после выхода из блока

Основная идея

Обновление

При изменении A[j] достаточно пересчитать только позиции в том же блоке, что и j (двигаясь справа налево).

Ответ на запрос

Начинаем с позиции i и прыгаем по блокам, суммируя количество шагов, пока не достигнем последней позиции.

Сложность: O((n + m) × √n) — предподсчёт O(n), запрос O(√n), обновление O(√n).

Формат ввода и вывода

Ввод:

Пример ввода:

5 4
2 1 3 2 1
1 1
0 3 1
1 1
1 4

Вывод:

5 3
4 2
4 1

Попробовать онлайн

Введите массив и запросы:

Результат появится здесь...