Сумма цифр факториала
Ссылка на исходную проблему
Это не вопрос домашней работы. Я просто подумал, что кто-то может знать реальное решение этой проблемы.
Я был на конкурсе программирования еще в 2004 году, и была эта проблема:
Учитывая n, найдите сумму цифр n!. n может быть от 0 до 10000. Ограничение по времени: 1 секунда. Я думаю, что для каждого тестового набора было до 100 номеров.
Мое решение было довольно быстрым, но не достаточно быстрым, поэтому я просто позволяю ему работать некоторое время. Он построил массив предварительно рассчитанных значений, которые я мог бы использовать в своем коде. Это был взлом, но он сработал.
Но был парень, который решил эту проблему примерно с 10 строками кода, и он дал бы ответ в кратчайшие сроки. Я считаю, что это было какое-то динамическое программирование или что-то вроде теории чисел. В то время нам было 16, поэтому это не должно быть "ракетной наукой".
Кто-нибудь знает, какой алгоритм он мог бы использовать?
РЕДАКТИРОВАТЬ: Извините, если я не поставил вопрос ясно. Как сказал mquander, должно быть умное решение, без bugnum, с простым кодом Паскаля, пара петель, O (n 2) или что-то в этом роде. 1 секунда больше не является ограничением.
Я нашел здесь, что если n > 5, то 9 делит сумму цифр факториала. Мы также можем найти количество нулей в конце номера. Можем ли мы это использовать?
Хорошо, еще одна проблема из конкурса программирования из России. Учитывая 1 <= N < 2 000 000 000, выход N! mod (N + 1). Это как-то связано?
Ответы
Ответ 1
Я не уверен, кто все еще обращает внимание на эту тему, но здесь все равно.
Во-первых, в официальной связанной версии он должен быть только 1000 факториальных, а не 10000 факториалов. Кроме того, когда эта проблема была повторно использована в другом конкурсе по программированию, ограничение времени составляло 3 секунды, а не 1 секунду. Это сильно влияет на то, как сложно работать, чтобы получить достаточно быстрое решение.
Во-вторых, для реальных параметров конкурса, решение Peter звучит, но с одним дополнительным завихрением вы можете ускорить его в 5 раз с 32-битной архитектурой. (Или даже коэффициент 6, если требуется только 1000!) А именно, вместо того, чтобы работать с отдельными цифрами, реализуйте умножение в базе 100000. Затем в конце суммируйте цифры в каждой суперзнаке. Я не знаю, насколько хорош компьютер, которого вы разрешили в конкурсе, но у меня есть настольный компьютер дома, который примерно такой же старый, как и конкурс. Следующий пример кода составляет 16 миллисекунд за 1000! и 2,15 секунды для 10000! Код также игнорирует завершающие 0s по мере их появления, но это только экономит около 7% работы.
#include <stdio.h>
int main() {
unsigned int dig[10000], first=0, last=0, carry, n, x, sum=0;
dig[0] = 1;
for(n=2; n <= 9999; n++) {
carry = 0;
for(x=first; x <= last; x++) {
carry = dig[x]*n + carry;
dig[x] = carry%100000;
if(x == first && !(carry%100000)) first++;
carry /= 100000; }
if(carry) dig[++last] = carry; }
for(x=first; x <= last; x++)
sum += dig[x]%10 + (dig[x]/10)%10 + (dig[x]/100)%10 + (dig[x]/1000)%10
+ (dig[x]/10000)%10;
printf("Sum: %d\n",sum); }
В-третьих, есть удивительный и довольно простой способ ускорить вычисление с помощью другого значимого фактора. При использовании современных методов умножения больших чисел для вычисления n! Не требуется квадратичное время. Вместо этого вы можете сделать это в O-тильде (n), где тильда означает, что вы можете бросить логарифмические факторы. Существует простое ускорение из-за Карацубы, которое не приносит временной сложности до этого, но все же улучшает его и может сэкономить еще один фактор 4 или так. Чтобы использовать его, вам также необходимо разделить факториал на равные диапазоны. Вы создаете рекурсивный алгоритм prod (k, n), который умножает числа от k на n по формуле псевдокода
prod(k,n) = prod(k,floor((k+n)/2))*prod(floor((k+n)/2)+1,n)
Затем вы используете Karatsuba для выполнения большого умножения.
Даже лучше, чем Карацуба - это алгоритм умножения Шонхаге-Штрассена на основе Фурье-преобразования. Как это бывает, оба алгоритма являются частью современных библиотек большого числа. Вычисление огромных факториалов быстро может быть важным для некоторых приложений чистой математики. Я думаю, что Шонхаге-Штрассен слишком много для конкурса на программирование. Karatsuba действительно прост, и вы можете представить это в решении A + проблемы.
Часть поставленного вопроса - это некоторые предположения, что существует простой трюк теории чисел, который полностью изменяет проблему конкурса. Например, если бы вопрос заключался в определении n! mod n + 1, то теорема Уилсона гласит, что ответ -1, когда n + 1 является простым, и это действительно простое упражнение, чтобы увидеть, что оно 2 при n = 3 и в противном случае 0, когда n + 1 является составным. Есть и вариации этого; например, n! также является весьма предсказуемым mod 2n + 1. Существуют также некоторые связи между конгруэнциями и суммами цифр. Сумма цифр x mod 9 также равна x mod 9, поэтому сумма равна 0 mod 9 при x = n! при n >= 6. Переменная сумма цифр x mod 11 равна x mod 11.
Проблема в том, что если вы хотите получить сумму цифр большого числа, а не по модулю ничего, трюки из теории чисел заканчиваются довольно быстро. Добавление цифр числа не очень хорошо связано с добавлением и умножением на переносы. Часто трудно пообещать, что математика не существует для быстрого алгоритма, но в этом случае я не думаю, что существует какая-либо известная формула. Например, я уверен, что никто не знает суммы цифр факториала googol, хотя это всего лишь несколько цифр с примерно 100 цифрами.
Ответ 2
Это A004152 в Онлайн-энциклопедия Integer Последовательности. К сожалению, у него нет полезных советов о том, как правильно его вычислять - его рецепты клена и математики принимают наивный подход.
Ответ 3
Я бы атаковал вторую проблему, чтобы вычислить N! mod (N + 1), используя теорему Вильсона. Это уменьшает проблему до проверки того, является ли N простым.
Ответ 4
Маленький быстрый python script найден в http://www.penjuinlabs.com/blog/?p=44. Это элегантная, но все же грубая сила.
import sys
for arg in sys.argv[1:]:
print reduce( lambda x,y: int(x)+int(y),
str( reduce( lambda x, y: x*y, range(1,int(arg)))))
$ time python sumoffactorialdigits.py 432 951 5436 606 14 9520
3798
9639
74484
5742
27
141651
real 0m1.252s
user 0m1.108s
sys 0m0.062s
Ответ 5
Предположим, что у вас большие числа (это наименьшая из ваших проблем, если предположить, что N действительно большой, а не 10000) и продолжить оттуда.
Трюк ниже - фактор N! факторизуя все n <= N, а затем вычислить степени факторов.
Есть вектор счетчиков; один счетчик для каждого простого номера до N; установите для них значение 0. Для каждого n <= N коэффициент n и соответственно увеличивайте счетчики простых коэффициентов (коэффициент умножим: начните с малых простых чисел, постройте простые числа при факторизации и помните, что деление на 2 является сдвигом). Вычтите счетчик 5 из счетчика 2 и сделайте счетчик равным 5 ноль (здесь никто не заботится о факторах 10).
Изменить
вычислите все простые числа до N, запустите следующий цикл
for (j = 0; j< last_prime; ++j) {
count[j] = 0;
for (i = N/ primes[j]; i; i /= primes[j])
count[j] += i;
}
конец редактирования
Обратите внимание, что в предыдущем блоке мы использовали (очень) небольшие числа.
Для каждого простого множителя P вам нужно вычислить P на мощность соответствующего счетчика, который берет время регистрации (счетчика), используя итеративное возведение в квадрат; теперь вам нужно умножить все эти степени простых чисел.
В целом у вас есть о N log (N) операциях с небольшими числами (log N простых факторов) и Log N Log (Log N) операции с большими числами.
Edit
и после улучшения редактирования, только N операций на небольших числах.
конец редактирования
НТН
Ответ 6
1 секунда? Почему вы не можете просто вычислить n! и добавить цифры? Это 10000 умножений и не более нескольких десятков тысяч дополнений, которые должны занимать примерно одну миллионную долю секунды.
Ответ 7
Посмотрим. Мы знаем, что вычисление n! для любого достаточно большого количества в конечном итоге приведет к числу с большим количеством конечных нулей, которые не вносят вклад в сумму. Как насчет сбрасывания нулей по пути? Это позволит немного уменьшить размер элемента?
Хм. Неа. Я просто проверил, и целочисленное переполнение все еще большая проблема даже тогда...
Ответ 8
Вы должны вычислить жир.
1 * 2 * 3 * 4 * 5 = 120.
Если вы хотите только вычислить сумму цифр, вы можете игнорировать конечные нули.
Для 6! вы можете сделать 12 x 6 = 72 вместо 120 * 6
Для 7! вы можете использовать (72 * 7) MOD 10
ИЗМЕНИТЬ.
Я написал ответ слишком быстро...
10 является результатом двух простых чисел 2 и 5.
Каждый раз, когда у вас есть эти 2 фактора, вы можете их игнорировать.
1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15...
1 2 3 2 5 2 7 2 3 2 11 2 13 2 3
2 3 2 3 5 2 7 5
2 3
Фактор 5 появляется в 5, 10, 15...
Затем после умножения на конечный ноль появится 5, 10, 15...
У нас много двух и трех... Мы скоро переполним: - (
Затем вам по-прежнему нужна библиотека для больших чисел.
Я заслуживаю того, чтобы его нивелировали!
Ответ 9
Даже без целых чисел произвольной точности это должно быть грубым. В заявлении проблемы, с которым вы связались, самый большой фактор, который нужно будет вычислить, будет 1000!. Это число с примерно 2500 цифрами. Так просто сделайте это:
- Выделить массив из 3000 байтов, причем каждый байт представляет одну цифру в факториале. Начните со значения 1.
- Повторное умножение класса школы на массив, чтобы вычислить факториал.
- Составьте цифры.
Выполнение повторных умножений является единственным потенциально медленным шагом, но я уверен, что 1000 умножений могут быть выполнены за секунду, что является наихудшим случаем. Если нет, вы можете заранее вычислить несколько значений "вехи" и просто вставить их в свою программу.
Одна потенциальная оптимизация: Исключите конечные нули из массива, когда они появляются. Они не повлияют на ответ.
ОБЫЧНОЕ ПРИМЕЧАНИЕ: Я беру подход к программированию и конкуренции. Вы, вероятно, никогда не сделаете этого в профессиональной работе.
Ответ 10
другое решение с использованием BigInteger
static long q20(){
long sum = 0;
String factorial = factorial(new BigInteger("100")).toString();
for(int i=0;i<factorial.length();i++){
sum += Long.parseLong(factorial.charAt(i)+"");
}
return sum;
}
static BigInteger factorial(BigInteger n){
BigInteger one = new BigInteger("1");
if(n.equals(one)) return one;
return n.multiply(factorial(n.subtract(one)));
}