← Все вопросы

Почему мемоизация рекурсии иногда даёт WA из-за неинициализированного «пустого» значения?

Задан 5 месяцев назад1.3к просмотров2 ответа
10

Делаю top-down ДП с массивом memo, инициализирую его -1 как «не посчитано». Но в задаче само значение ответа может быть -1 (или 0), и кэш ломается: посчитанное -1 воспринимается как «не посчитано» и пересчитывается, иногда с WA. Как правильно?

2 ответа

13
✓ Принятый ответ — помог автору

Классическая ошибка: сентинел «не посчитано» совпал с возможным валидным ответом. Решения:

  1. Возьми сентинел заведомо вне диапазона ответа. Если ответ может быть -1, инициализируй memo значением, которого ответ принять не может (например INT_MIN или специально выбранное -2e9).
  2. Отдельный флаг «посчитано». Самый надёжный — массив bool computed[...] (или vector<bool>), не завязанный на значение:
int solve(int i) {
    if (computed[i]) return memo[i];
    computed[i] = true;
    int res = /* ... рекурсия ... */;
    return memo[i] = res;
}

Флаг ставим до рекурсивных вызовов в задачах с возможной цикличностью состояний, чтобы не зациклиться. Это убирает зависимость корректности кэша от диапазона значений.

Сложность не меняется — флаг добавляет O(states) памяти. Зато уходит коварный WA, который воспроизводится только когда настоящий ответ равен сентинелу.

6

Ещё одна засада в той же области — memset(memo, -1, ...) для не-char типов. memset пишет побайтово, и -1 корректно заполняет int всеми единицами (получается -1), но если попробуешь memset(..., 0x3f, ...) для INF — получишь 0x3f3f3f3f (≈10^9), что удобно как «бесконечность» именно потому, что INF+INF не переполняет int. А вот memset(memo, 1, ...) НЕ даст 1 в каждом int (даст 0x01010101). Поэтому для произвольных значений инициализации используй fill/std::fill, а memset — только для 0, -1 и трюка 0x3f.

Ваш ответ

Войдите, чтобы ответить на вопрос.