Для n
заданного целого числа вычислить набор n
случайных уникальных целых чисел в диапазоне 1..n^2
(включительно) так, чтобы сумма набора была равнаn^2
Случайный, в этом случае, означает равномерно случайный между действительными выходами. Каждый действительный выход для данного n
должен иметь единый шанс быть сгенерированным.
Например, n=3
должны иметь 1/3 шансу каждый из вывода 6, 1, 2
, 3, 5, 1
или 4, 3, 2
. Поскольку это набор, порядок не имеет значения, 4, 3, 2
идентичен3, 2, 4
счет
Победителем является программа, которая может вычислить максимум n
за 60 секунд.
Примечание. Для предотвращения возможного частичного жесткого кодирования все записи должны быть не более 4000 байтов.
тестирование
Весь код будет выполняться на моем локальном компьютере с Windows 10 (Razer Blade 15, 16 ГБ ОЗУ, 6-ядерный процессор Intel i7-8750H, 4,1 ГГц, GTX 1060 на случай злоупотребления графическим процессором), поэтому предоставьте подробные инструкции по запуску кода на моя машина.
По запросу записи могут быть запущены либо через Debian на WSL, либо на виртуальной машине Xubuntu (обе на той же машине, что и выше)
Работы будут проходить 50 раз подряд, итоговый результат будет в среднем из всех 50 результатов.
источник
Ответы:
Ржавчина , n ≈ 1400
Как бегать
Сборка
cargo build --release
и запуск сtarget/release/arbitrary-randomness n
.Эта программа работает быстрее всего с большим количеством памяти (конечно, если она не обменивается). Вы можете настроить использование памяти, отредактировав
MAX_BYTES
константу, в настоящее время установленную на 8 ГиБ.Как это работает
Множество строится с помощью последовательности двоичных решений (каждое число либо внутри, либо за пределами множества), каждая из вероятностей которых вычисляется комбинаторно путем подсчета числа возможных множеств, которые можно построить после каждого выбора с использованием динамического программирования.
Использование памяти для больших n уменьшается с помощью версии этой стратегии биномиального разбиения .
Cargo.toml
src/main.rs
Попробуйте онлайн!
(Примечание: версия TIO имеет несколько модификаций. Во-первых, ограничение памяти сокращено до 1 ГиБ. Во-вторых, поскольку TIO не позволяет вам писать a
Cargo.toml
и зависеть от внешних ящиков, напримерrand
, я вместо этого извлекdrand48
из библиотеки C, используя FFI. Я не удосужился посеять его, поэтому версия TIO будет давать одинаковый результат при каждом запуске. Не используйте версию TIO для официального бенчмаркинга.)источник
ln_add_exp
, проверяя, превышает ли абсолютная разница ~ 15 или около того, что может быть быстрее, если таких дополнений много.ln_add_exp
вызовы включают сопоставимые входы.Java 7+, n = 50 через ~ 30 секунд на TIO
Версия без ответа моего ответа для кода-версии этой задачи на данный момент, только с одним незначительным изменением:
java.util.Random#nextInt(limit)
используется вместо(int)(Math.random()*limit)
целого числа в диапазоне[0, n)
, поскольку это примерно в два раза быстрее .Попробуйте онлайн.
Объяснение:
Используемый подход:
Код разбит на две части:
n
количества случайных целых чисел, в которыхn squared
.Шаг 1 выполняется с помощью следующих подэтапов:
1) Создайте массив
n-1
количества случайных целых чисел в диапазоне[0, n squared)
. И добавить0
иn squared
в этот список. Это сделано вO(n+1)
исполнении.2) Затем он будет сортировать массив с помощью встроенного модуля.
java.util.Arrays.sort(int[])
Это будет сделано с точки зренияO(n*log(n))
производительности, как указано в документации:3) Рассчитайте разницу между каждой парой. Этот результирующий список различий будет содержать
n
целые числа, которые суммируются сn squared
. Это сделано вO(n)
исполнении.Вот пример:
Таким образом, эти три шага выше довольно хороши для производительности, в отличие от шага 2 и цикла вокруг всего, что является основной грубой силой. Шаг 2 разделен на следующие подэтапы:
1) Список различий уже сохранен в
java.util.Set
. Он проверит, равен ли размер этого набораn
. Если это так, это означает, что все случайные значения, которые мы сгенерировали, являются уникальными.2) И он будет также проверить , что она не содержит
0
в наборе, так как вызов просит случайные величины в диапазоне[1, X]
, гдеX
естьn squared
минус суммы[1, ..., n-1]
, как заявлено @Skidsdev в комментариях ниже.Если любой из двух указанных выше вариантов (не все значения уникальны или присутствует ноль), он сгенерирует новый массив и снова установит его, сбросив на шаг 1. Это продолжается до тех пор, пока мы не получим результат. Из-за этого время может сильно отличаться. Я видел, как он закончился за 3 секунды один раз на TIO
n=50
, но также и за 55 секунд один разn=50
.Доказательство единообразия:
Я не совсем уверен, как доказать это, чтобы быть полностью честным. Это
java.util.Random#nextInt
точно, как описано в документации:Различия между этими (отсортированными) случайными значениями, конечно, не являются одинаковыми, но наборы в целом являются однородными. Опять же, я не уверен, как это доказать математически, но вот сценарий, который поместит
10,000
сгенерированные наборы (дляn=10
) в карту со счетчиком , где большинство наборов являются уникальными; некоторые повторяются дважды; и максимальное повторное вхождение обычно находится в диапазоне[4,8]
.Инструкции по установке:
Поскольку Java является довольно известным языком с большим количеством информации о том, как создавать и запускать код Java, я буду кратким.
Все инструменты, используемые в моем коде, доступны в Java 7 (возможно, даже уже в Java 5 или 6, но давайте использовать 7 на всякий случай). Я почти уверен, что Java 7 уже заархивирована, поэтому я бы предложила загрузить Java 8 для запуска моего кода.
Мысли об улучшениях:
Я хотел бы найти улучшение для проверки на нули и проверить, что все значения уникальны. Я мог бы проверить это
0
раньше, убедившись, что случайного значения, которое мы добавляем в массив, еще нет, но это будет означать пару вещей: массив должен быть таким,ArrayList
чтобы мы могли использовать встроенный метод.contains
; цикл while следует добавлять до тех пор, пока мы не найдем случайное значение, которого еще нет в списке. Поскольку проверка на ноль теперь выполняется с.contains(0)
помощью набора (который проверяется только один раз), для производительности, скорее всего, лучше проверить его в этот момент, чем для добавления цикла с.contains
в список, который будет проверяться как минимумn
раз , но скорее всего больше.Что касается проверки уникальности, у нас есть только
n
количество случайных целых чисел, которое суммируетсяn squared
после шага 1 программы, поэтому только тогда мы можем проверить, все ли уникальны или нет. Может быть возможно сохранить сортируемый список вместо массива и проверить различия между ними, но я серьезно сомневаюсь, что это улучшит производительность, чем просто помещает их в aSet
и проверяет, не равен ли размер этого набораn
один раз.источник
n^2 - sum(1..n-1)
например, дляn=5
самого большого действительного числа5^2 - sum(1, 2, 3, 4) == 25 - 10 == 15
n
, не так ли? В этом случае вы можете добавить0
в набор, а затем проверить, что размер (сейчас) больше, чемn
. Это может произойти, только если все различия отличны от нуля и различны.HashSet.contains
в большинстве случаев близокO(1)
, а в худшем случае -O(n)
в Java 7 иO(log n)
в Java 8+ (он был улучшен после замены цепочки на обнаружение коллизий). Если мне разрешено возвращать Set с добавленным0
для проверки, то это действительно немного лучше для производительности, но если мне нужно позвонитьset.remove(0);
внутри if, я почти уверен, что производительность примерно такая же.Mathematica n = 11
источник