Объясните этот алгоритм O (n log n) для проблемы броска кошки/яйца

Эта проблема (Сколько кошек нужно выбрасывать из здания, чтобы определить максимальный пол, где такая кошка выживет. На самом деле жестоко, имеет принятый ответ с сложностью O (n ^ 3). Проблема эквивалентна этому Google Jam, который должен быть разрешен для N = 2000000000.

Кажется, что решение O (n ^ 3) недостаточно для его решения. Из поиска на странице решений решение jdmetz (# 2) представляется O (n log n). Я не совсем понимаю алгоритм. Может кто-нибудь объяснить это?

Изменить

Ответ 1

Верхнее решение на самом деле O(D*B) (с использованием проблемной терминологии), но автор заметил, что для большинства ответов D и B будет больше, чем 2^32, и, таким образом, -1 может быть возвращен.
Итак, он вводит maxn равный 1100 - максимальный D и F, для которого имеет смысл подсчитать.
Для D, B = 10000 он сразу возвращает -1. Для D, B = 100 используется рекурсивная формула ниже. Только для некоторых "угловых значений", таких как D = 10000, B = 2, используется прямая формула. (см. его комментарии для получения подробной информации о "прямой формуле" )

Для D, B < maxn автор предоставляет формулу в комментариях: f(d,b) = f(d-1,b)+f(d-1,b-1)+1. Функция F здесь возвращает максимальное количество этажей, для которых вы можете определить "точку разрыва", используя не более D попыток и не более B яиц.
Сама формула должна быть самоочевидной: независимо от того, на каком этаже мы бросаем первое яйцо, есть два результата. Он может сломаться, затем мы можем проверить до f(d-1, b-1) этажей ниже. Или он может "выжить", тогда мы можем проверить до f(d-1, b) этажей выше. С текущим полом это делает его f(d-1,b)+f(d-1,b-1)+1 total.

Теперь он может быть легко закодирован как DP (динамическое программирование). Если вы оставите бесконечность (2^32), то она станет довольно чистой.

    for (int i = 1; i < maxn; ++i) {
        for (int j = 1; j < maxn; ++j) {
            f[i][j] = f[i - 1][j - 1] + f[i - 1][j] + 1;
        }
    }

Когда у нас есть массив f[D][B], сохраняющий эти результаты, поиск D' и B' является двоичным поиском. (так как функция F монотонно растет как с помощью D, так и B)

PS Хотя я и сказал в ответ на проблему "кошек" более быстрое решение, это на самом деле круче, чем я имел в виду:) Спасибо вам и sclo (автор)!

Ответ 2

Сильно отредактирован из-за исправления вопроса

Рассмотрим функцию f (x, y), которая дает количество этажей, которые вы можете проверить, с пределом x бросков и y смертей. Если первый бросок приводит к смерти, у вас есть x-1 броски и y-1 смерти, поэтому вы можете проверить полы f (x-1, y-1). Если первый бросок не приведет к смерти, у вас останутся x-1 броски и y смертей, поэтому вы можете проверить f (x-1, y) этажи выше того, из которого вы бросили. Поэтому мы имеем рекуррент f (x, y) = f (x-1, y-1) + f (x-1, y) + 1. Базовые условия заключаются в том, что f (x, 0) = 0 (поскольку if вы делаете даже один бросок, вы рискуете смертью, поэтому вы не можете делать броски и не можете даже проверить первый этаж), а f (1, x) = 1, где x > 0 (вы должны сделать единственный бросок из первый этаж, потому что, если вы выбросите с более высокого этажа и получите смерть, у вас нет результата).

Теперь рассмотрим функцию g (x, y) = SUM 1 <= r <= y of x C r. (Что биномиальный коэффициент, х выбирает г. Я не думаю, что на этом сайте поддерживается обозначение TeX). Утверждение состоит в том, что f (x, y) = g (x, y). Чтобы проверить базовые случаи, рассмотрим, что g (x, 0) представляет собой сумму по 0 элементам, так что соответствует; и g (1, y), где y > 0 дает 1 C 1= 1. Поэтому остается только проверить, что g удовлетворяет рекуррентности.

g (x-1, y-1) + g (x-1, y) + 1 = SUM 1 <= r <= y-1 x-1 C r + SUM 1 <= r <= y x-1 C r + 1

g (x-1, y-1) + g (x-1, y) + 1 = SUM 2 <= r <= y x-1 C r-1 + SUM 1 <= r <= y x-1 C r + 1

g (x-1, y-1) + g (x-1, y) + 1 = SUM 2 <= r <= y x-1 C r-1 + SUM 1 <= r <= y x-1 C r + x-1 C 0к югу >

g (x-1, y-1) + g (x-1, y) + 1 = SUM 1 <= r <= y x-1 C r-1 + SUM 1 <= r <= y x-1 C r

g (x-1, y-1) + g (x-1, y) + 1 = SUM 1 <= r <= y (x-1 C r- 1 + x-1 C r)

g (x-1, y-1) + g (x-1, y) + 1 = SUM 1 <= r <= y (x C r)

g (x-1, y-1) + g (x-1, y) + 1 = g (x, y)

КЭД

На самом деле это можно получить непосредственно, рассматривая его как комбинаторную задачу. Если мы в полной мере используем каждый бит полученной информации, то каждая возможная последовательность выживания или смерти соответствует разному максимальному этажу. Например. за три броска, одну разрешенную смерть, возможности - смерть; Выживание смерть; выживание выживание смерть; или выживаемость выживания. Тем не менее, мы должны уклоняться от случая, когда нет смерти, потому что в этом случае мы не определили пол. Поэтому, если у нас есть x бросков и y разрешенных смертей, мы можем иметь фактическое число смертей r от 1 до y, и для каждого из них мы имеем x C r последовательности. (Если r = y, то любые "пережитки" после смерти y th фактически "не выбрасываются" ).

jdmetz решение для F состоит из оценки g (D, B). Это невозможно сделать лучше, чем время O (min (D, B)), так как для этой суммы нет замкнутой гипергеометрической формы (этот факт можно доказать с использованием алгоритма Госсера).

Добавление Фактически все три части исходной задачи могут быть выполнены в линейном времени (при условии, что умножение и арифметика являются операциями с постоянным временем, которые мы имеем до сих пор - не верно, но мы оставим это в стороне). Операция O (n lg n) в решении jdmetz находит наименьшее D такое, что f (D, B) > F.

Если объединить нашу исходную рекуррентность f (D, B) = f (D-1, B-1) + f (D-1, B) + 1 с разностью термов от формы g, f (D, B) = f (D, B-1) + D C B получаем f (D, B) = 2 * f (D-1, B) + 1 - D-1 C B. Затем мы можем использовать a C b= a-1 C b * a/(a ​​- b) to цикл над D из 1.

    private static int d_opt(final long f, final int b)
    {
        int d = 0, dCb = 0;
        long f_db = 0;
        while (f_db < f)
        {
            dCb = (d == b) ? 1 : d * dCb / (d-b);
            f_db = 2 * f_db + 1 - dCb;
            d++;
        }
        return d;
    }