Редакция для Лягушка с прыжком до K
Submitting an official solution before solving the problem yourself is a bannable offence.
1. Идея
Нужно найти минимальную суммарную стоимость пути от первого камня до последнего.
Естественная идея — динамическое программирование.
Будем хранить dp[i] — минимальное усилие, нужное, чтобы попасть на камень i.
Тогда, чтобы прийти на камень i, лягушка могла прыгнуть на него с одного из предыдущих камней:
i - 1i - 2- ...
i - K
если такие индексы существуют.
Если последний прыжок был с камня j на камень i, то стоимость такого варианта равна:
dp[j] + |h[i] - h[j]|
Значит, надо перебрать все допустимые j и взять минимум.
2. Наблюдения
На первый камень лягушка уже стоит, поэтому:
dp[0] = 0.Для каждого камня
iдостаточно смотреть только на последниеKкамней перед ним, потому что прыгать дальше, чем наK, нельзя.Ограничение
K <= 100очень важно: даже еслиnбольшое, перебор до100вариантов для каждого камня вполне подходит.Ответ может быть большим, поэтому нужно использовать 64-битные целые числа:
- в C++ это
long long, - в Python проблем нет, там целые числа длинные автоматически.
- в C++ это
3. Алгоритм
- Считать
n,Kи массив высотh. - Создать массив
dpдлиныn, заполненный большим числом. - Положить
dp[0] = 0. - Для каждого камня
iот1доn - 1:- найти левую границу:
left = max(0, i - K) - перебрать все
jотleftдоi - 1 - посчитать стоимость перехода:
dp[j] + abs(h[i] - h[j]) - обновить минимум для
dp[i]
- найти левую границу:
- Вывести
dp[n - 1].
4. Почему это работает
Докажем, что такой переход действительно находит оптимальный ответ.
Рассмотрим некоторый камень i. Любой путь в i заканчивается прыжком с какого-то камня j, где i - K <= j < i, потому что длина прыжка не может превышать K.
Если мы уже знаем минимальную стоимость добраться до j, то стоимость пути в i через j равна:
dp[j] + |h[i] - h[j]|
Среди всех допустимых последних камней j нужно выбрать тот, который дает минимальную сумму. Значит,
dp[i] — это минимум по всем таким j.
Почему этого достаточно?
- любой допустимый путь в
iобязательно имеет некоторый последний прыжок; - для этого последнего прыжка мы рассматриваем соответствующий
j; - стоимость лучшего пути до
jуже хранится вdp[j]; - значит, минимум по всем таким вариантам и есть оптимальный ответ для
i.
Так как мы считаем dp слева направо, то в момент вычисления dp[i] все нужные значения dp[j] уже известны.
Следовательно, после заполнения массива dp значение dp[n - 1] равно минимальному суммарному усилию до последнего камня.
5. Сложность
Для каждого камня перебирается не более K предыдущих камней.
Итоговая сложность:
O(n * K)
Память:
O(n)
При n <= 10^5 и K <= 100 это работает быстро.
6. Код на C++17
#include <iostream>
#include <vector>
#include <cmath>
#include <climits>
using namespace std;
int main() {
int n, K;
cin >> n >> K;
vector<long long> h(n);
for (int i = 0; i < n; i++) {
cin >> h[i];
}
const long long INF = LLONG_MAX / 4;
vector<long long> dp(n, INF);
dp[0] = 0;
for (int i = 1; i < n; i++) {
int left = max(0, i - K);
for (int j = left; j < i; j++) {
long long cost = dp[j] + llabs(h[i] - h[j]);
if (cost < dp[i]) {
dp[i] = cost;
}
}
}
cout << dp[n - 1] << '\n';
return 0;
}
7. Код на Python 3
n, K = map(int, input().split())
h = list(map(int, input().split()))
INF = 10**30
dp = [INF] * n
dp[0] = 0
for i in range(1, n):
left = max(0, i - K)
best = INF
for j in range(left, i):
cost = dp[j] + abs(h[i] - h[j])
if cost < best:
best = cost
dp[i] = best
print(dp[n - 1])
Комментарии