В свете большого количества проблем я подумал, что этот может быть интересным.
В этой задаче мы будем использовать систему счисления остатков (RNS) для сложения, вычитания и умножения больших целых чисел.
Что такое RNS
RNS является одним из многих способов, которые люди разработали для идентификации целых чисел. В этой системе числа представлены последовательностью вычетов (которые являются результатами после операции модуля (то есть остатком после целочисленного деления)). В этой системе каждое целое число имеет много представлений. Для простоты мы собираемся ограничить вещи так, чтобы каждое целое число было уникально представлено. Я думаю, что проще описать, что происходит на конкретном примере.
Давайте посмотрим на первые три простых числа: 2, 3, 5. В системе RNS мы можем использовать эти три числа для уникального представления любого числа, которое меньше, чем 2 * 3 * 5 = 30, используя вычеты. Возьми 21:
21 меньше 30, поэтому мы можем представить его, используя результаты после модификации на 2, 3 и 5. (т. Е. Остаток после целочисленного деления на 2, 3 и 5)
Мы бы идентифицировали 21 со следующей последовательностью целых чисел:
21 ~ {21 мод 2, 21 мод 3, 21 мод 5} = {1, 0, 1}
И поэтому в нашей системе RNS вместо «21» мы будем использовать {1,0,1}.
В общем случае, учитывая целое число n , мы представляем n как { n mod 2, ..., n mod p_k }, где p_k - наименьшее простое число, такое, что n меньше, чем произведение всех простых чисел, меньше или равно p_k .
Другой пример, скажем, у нас 3412. Нам нужно использовать 2,3,5,7,11,13 здесь, потому что, 2*3*5*7*11*13=30030
тогда как, 2*3*5*7*11=2310
который слишком мал.
3412 ~ {3412 мод 2, 3412 мод 3, 3412, мод 5, ..., 3412 мод 13} = {0, 1, 2, 3, 2, 6}
Вы замечаете, что используя эту систему, мы можем представлять очень большие цифры относительно безболезненно. Используя остатки {1, 2, 3, 4, 5, 6, 7, 8, ...}, мы можем представить числа до {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} соответственно. ( Вот серия )
Наша задача
Мы будем использовать эти остатки для выполнения +, - и * на больших числах. Я опишу эти процессы ниже. На данный момент вот спецификации ввода и вывода.
вход
Вам будет дано два (потенциально очень больших) числа через аргумент stdin или function. Они будут даны как строки из 10 цифр.
Для дальнейшего описания проблемы мы называем первый вход n
и второй m
. Предположим, что n> m> = 0 .
Вам также будет дано +
или -
или *
для указания операции для выполнения.
Выход
Пусть х целое число. Мы будем использовать [ x ] для ссылки на RNS-представление, описанное выше для x .
Вы должны вывести [n] <operator> [m] = [result]
Как выполнять операции в РНС
Эти операции относительно просты. Учитывая два числа в обозначениях RNS, чтобы сложить, вычесть или умножить их, просто выполните данные операции компонентно, а затем возьмите модуль.
т.е.
{1, 2, 3} + {1, 1, 4} = {(1 + 1) mod 2, (2 + 1) mod 3, (3 + 4) mod 5} = {0, 0, 2}
Обратите внимание, что если число остатков, используемых для представления двух разных чисел, не одинаково, при выполнении операций вам нужно будет увеличить «более короткое» число, чтобы оно имело одинаковое количество остатков. Это следует тому же процессу. Смотрите тестовые примеры для примера.
То же самое происходит, если результат требует больше остатков, чем любой из входных данных. Затем оба входа должны быть «расширены».
Важные детали
Здесь мы будем иметь дело с большими числами, но не произвольно большими. Мы будем нести ответственность за числа до произведения первых 100 простых чисел (см. Ниже). Для этого вам бесплатно дается первые 100 простых чисел (без стоимости байтов) . Вы можете вставить их в массив, называемый
p
или что-то идиоматическое для вашего языка, а затем вычесть количество байтов, использованных для инициации этого массива, из вашего итогового итога. Это, конечно, означает, что они могут быть жестко запрограммированы или вы можете использовать встроенный для их генерации.Если по какой-либо причине это целочисленное представление по умолчанию, используемое на вашем языке. Это хорошо.
Вы не можете использовать любой тип произвольного целочисленного типа, если он не является языком по умолчанию. Если это значение по умолчанию, вы не можете использовать его для хранения целых чисел, которые обычно не помещаются в 64 бита.
Чтобы было ясно, каждое целое число всегда будет представлено с наименьшим возможным остатком. Это касается как ввода, так и вывода.
Я думаю, что другие спецификации должны предотвращать это, но быть избыточными: вы не можете выполнять данную операцию на входах, а затем и затем изменить все на RNS и затем вывести. Вы должны изменить входные данные на RNS, а затем выполнить операции для получения выходных данных.
Тестовые случаи
Входные данные:
n = 10
m = 4
+
Выход:
{ 0, 1, 0 } + { 0, 1 } = { 0, 2, 4 }
Объяснение:
Во-первых, измените каждое число на его представление RNS, как описано выше:
10 ~ {0,1,0}
и 4 ~ {0,1}
. Обратите внимание, что когда мы хотим сделать компонентное сложение, в 10
нем больше компонентов, чем 4
. Поэтому мы должны «расширить» более короткое число. Поэтому мы вкратце напишем 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}
. Теперь приступим к сложению, а затем возьмем модуль.
- вход
n=28
m=18
+
Выход:
[ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
- Ввод (я растираю лицо на клавиатуре)
n=1231725471982371298419823012819231982571923
m=1288488183
*
Вывод (разбит на отдельные строки для удобства чтения):
[1, 2, 3, 6, 2, 10, 2, 1, 12, 16, 7, 15, 34, 29, 31, 5, 55, 32, 66, 61, 3, 76, 52, 14, 65, 44, 99, 57 ]
*
[1, 0, 3, 3, 4, 8, 9, 10, 8, 0 ]
=
[1, 0, 4, 4, 8, 2, 1, 10, 4, 0, 17, 7, 27, 21, 44, 51, 56, 9, 6, 9, 12, 0, 52, 36, 43, 68, 99, 24, 96, 39, 96, 66, 125]
n
требует 28 простых чисел. m
требует 10. n*m
требует 33.
- вход
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-
Выход:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 509]
-
[0, 2, 1, 6, 1, 12, 11, 18, 14, 28, 21, 36, 37, 42, 16, 52, 41, 60, 16, 70, 49, 78, 80, 88, 49, 100, 13, 106, 4, 112, 68, 130, 36, 138, 37, 150, 0, 162, 8, 172, 163, 180, 18, 192, 129, 198, 135, 222, 78, 228, 90, 238, 57, 250, 36, 262, 87, 270, 206, 280, 193, 292, 253, 310, 224, 316, 57, 336, 48, 348]
=
[0, 1, 4, 1, 10, 1, 6, 1, 9, 1, 10, 1, 4, 1, 31, 1, 18, 1, 51, 1, 24, 1, 3, 1, 48, 1, 90, 1, 105, 1, 59, 1, 101, 1, 112, 1, 0, 1, 159, 1, 16, 1, 173, 1, 68, 1, 76, 1, 149, 1, 143, 1, 184, 1, 221, 1, 182, 1, 71, 1, 90, 1, 54, 1, 89, 1, 274, 1, 299, 1, 266, 1, 228, 1, 340, 1, 170, 1, 107, 1, 340, 1, 88, 1, 157, 1, 143, 1, 22, 1, 22, 1, 58, 1, 296, 1, 371, 1, 140]
n
использует 100 простых чисел. m
использует 70 простых чисел. n-m
использует 99 простых чисел.
Я проверил их, используя ChineseRem
встроенную реализацию китайской теоремы об остатках для GAP (которая в основном берет числа RNS и заменяет их на целые 10). Я считаю, что они верны. Если что-то кажется подозрительным, пожалуйста, дайте мне знать.
Для тех, кто заботится, продукт первых 100 простых чисел:
471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090
Это число на 1 больше максимального числа, которое мы можем представить, используя данную систему (и ограничение 100 простых чисел).
(a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))
в ES6, например. Я думаю, что самая сложная часть - это, вероятно, найти число простых чисел, необходимое для представления результата без использования арифметики произвольной точности, хотя последующее преобразование в RNS не совсем тривиально.1234,1234,+
)?Ответы:
GAP
Немного предыстории: я признаю, что когда я создавал этот вопрос много месяцев назад, у меня не было метода для решения трудной части этого вопроса: определения правильного числа простых чисел для использования. На этом сайте много очень умных людей, и я действительно ожидал, что кто-то найдет способ сделать это довольно быстро. Однако, поскольку этого не произошло, я даже не был уверен, что эту проблему действительно можно решить. Итак, мне пришлось потратить время на разработку метода. Я считаю, что то, что я сделал, не нарушает ни одного из правил в этом испытании, конечно, я хотел бы, чтобы это было проверено фактом.
Я также немного сожалею о выборе code-golf, потому что решения немного глубже, чем обычно бы подходили для формата тегов. Сказав это, чтобы следовать правилам сайта, в нижней части этого поста есть версия моего решения для игры в гольф.
Код
Объяснение:
Для начала мы рассчитаем все 100 остатков для обоих входов. Мы делаем это с помощью
modulus
функции в коде. Я старался быть осторожным, чтобы мы использовали встроеннуюmod
функцию после каждого шага. Это гарантирует, что у нас никогда не будет числа, которое больше, чем540^2
на 1 меньше 100-го простого квадрата.После того, как у нас есть все остатки, мы можем выполнить данную операцию и
mod
каждую запись снова. Теперь у нас есть уникальный указатель для результата, но нам нужно определить минимальное количество записей, которые мы должны использовать для представления результата и каждого из входных данных.Выяснение того, сколько остатков нам действительно нужно, является самой сложной частью этой проблемы. Чтобы определить это, мы выполним большинство шагов китайской теоремы об остатках (CRT). Тем не менее, мы, очевидно, должны внести изменения, чтобы мы не получили слишком большие числа.
Позвольте
prod(i)
быть сумма первыхi-1
простых чисел. Например,Позвольте
X
быть целым числом. Позвольте{r_i}
быть остаткиX
, то естьГде
p_i
этоi
й премьер. Это для1<i<=100
нашего случая.Теперь мы будем использовать CRT , чтобы найти последовательность
{u_i}
таким образом, что сумма поi
отprod(i) * u_i
равнаX
. Обратите внимание, что каждыйu_i
также технически является остатком, какu_i < p_i
. Кроме того, еслиX < prod(i)
потомu_i = 0
. Это имеет решающее значение. Это означает, что, изучая конечные нули, мы можем определить, сколько остатковr_i
нам действительно нужно представитьX
в RNS.Если вы хотите изучить некоторые последовательности
u_i
,partial_chinese
функция возвращаетu_i
последовательность.Возиться с CRT, я смог найти рекурсивную формулу для
u_i
значений, решая вопрос определения, сколько остатков нам нужно.Формула:
Где
SUM
это сумма поj in [1,i)
изu_j * prod(i)
.Конечно,
prod(i)
в некоторых случаях его невозможно рассчитать, потому что он слишком велик. Для этого я использовалphi_i
функцию. Эта функция возвращаетprod(j) (mod p_i)
. Это происходитmod
на каждом шагу, поэтому мы никогда не вычисляем слишком большие значения.Если вам интересно, откуда взялась эта формула, я бы порекомендовал поработать над несколькими примерами CRT, которые можно найти на странице википедии .
Наконец, для каждого входа, а также для нашего вывода мы вычисляем
u_i
последовательность и затем определяем конечные нули. Затем мы выбрасываем это многоr_i
из конца последовательностей вычетов.Код "Golfed", 2621 байт
источник
Mathematica, не игра в гольф
Функция
rns[d_,l_]
преобразует целое число-10 в целое числоd
RNS длиныl
.Функция
plus
/times
/subtract
добавить / умножить / вычесть одно целое число RNS в / из другого, оба из которых имеют одинаковую длину.Функция
mag[f_]
оценивает приблизительную величину числа с плавающей запятойf
в терминах нижней границы длины своего представления RNS.Функция
ext[m_,n_,i_]
узнает остаток от деления на произведениеm
иPrime[Range@n]
наPrime[i]
.Функция
multi[e_,p_,t_]
дает наименьший множитель,m
удовлетворяющийDivisible[m*e+t,p]
Функция
appx[d_]
берет первые6
цифры десятичного целого числа и выдает приблизительное значение с плавающей запятой.С помощью вышеперечисленных функций теперь мы можем решить непростую задачу - определить длину результата .
Во-первых, я должен уточнить, что определить длину RNS целого числа нелегко. Для небольших целых чисел мы можем сравнить их непосредственно с произведением простых чисел. Но для очень больших целых чисел, поскольку запрещено вычислять произведение простых чисел бесконечно точно, такое сравнение больше не работает.
Например, при условии , что продукт простого
1
To30
есть3.16*10^46
, длина РНС целых чисел вокруг3.16*10^46
может быть возможна29
или30
. Функцияmag
даст29
в качестве ссылки для этих целых чисел, показывая, что оба29
и30
возможны.Зная величину, мы просто представляем целое число в соответствии с этой величиной напрямую, надеясь вычислить его истинную длину. Хитрость здесь в том, чтобы добавить несколько новых чисел к исходному номеру и изменить его представление RNS, пока представление не станет нулевым.
Например,
mag[211.]
есть4
, а его4
представление длины есть{1, 1, 1, 1}
.Добавляя некоторое число, мы увеличиваем
211
до наименьшего числа, которое делится на210
(2*3*5*7
). И теперь мы заключаем, что исходное число больше, чем210
, поскольку420
равно «примерно» в два раза210
. Нетрудно представить, что если мы начнем209
, то окончательное число будет «приблизительно»210
.Функция
length[f_,n_]
принимает значение с плавающей запятой,f
чтобы оценить величину, и скорректировать ее на основе своего представления RNSn
.Функция
rnsOperation[a_,b_,op_,rnsop_]
даетrnsop[a,b]
иop
соответствует нормальным операциям, используемым для получения приблизительных результатов на основе значений с плавающей запятой.пример
источник
Python 3 , 435 байт
Эта проблема была в моем списке некоторое время, но только недавно: а) я потратил время и внимание на фактическую попытку ответа; и б) фактически проверил мою идею вычислить размер чисел (и, таким образом, число простых чисел, сравнивая его с размером примитивов), используя некую нечестивую комбинацию логарифмов и теоремы об остатках в Китае. К сожалению, работа с логарифмами при попытке определить количество простых чисел, которые, например,
large_primorial + 3
требуются, означала, что мне пришлось искать способы обхода проблем с плавающей запятой.И так, это порт ответа Лиама .
Попробуйте онлайн!
объяснение
Пытаясь перенести ответ Лиама, я лично обнаружил, что некоторые из представленных объяснений были смущающе сформулированы, так что это моя попытка объяснить его алгоритм.
Во-первых, мы получаем остатки
n
иm
.Это включает в себя превращение всех цифр входных строк и превращение их в числа по модулю каждого из наших простых чисел, например, для 28, мы бы
[(20 + 8) mod 2, (20 + 8) mod 3, (20 + 8) mod 5, etc]
Затем мы добавляем, умножаем или вычитаем остатки попарно, используя
eval()
Затем мы получаем размеры наших остатков, то есть минимальное количество простых чисел, которые нам нужны.
Получение размеров - самая сложная и интенсивная часть кода. Мы используем
partial_chinese
функцию, которая дает нам нашу последовательность,u_i
чтобы определить размер с. Больше наu_i
мгновение.Последовательность
u_i
вычисляется путемr_i
вычитания каждого остатка , вычитания суммыu_j * primorial(j) for j in [1, i)
, а затемdividing
поprimorial(i)
модулюprimes[i]
. То естьu_i = (r_i - SUM) / primorial(i)
. Подробнее о наших основных и разделительных функциях.phi_i(j, i)
рассчитываетprimorial(j) mod primes[i]
. Отдел по модулю любого простогоp
легко реализуется путем проверки мультипликативных инверсий вручную, как мы можем быть уверены , что любой возможныйu_i
будет0 <= u_i < p
гарантировано быть взаимно просты с р и т гарантируется мультипликативный обратный.После всего этого мы печатаем нашу строку и все готово.
Что дальше
Это было весело реализовать. Я все еще хочу посмотреть, могу ли я каким-то образом использовать логарифмы в другом ответе. И я хотел бы реализовать этот код или что-то вроде функционального языка игры в гольф, например, APL или Jelly. Любые предложения и исправления в гольф приветствуются!
источник