2009-09-24 3 views
48

Link to the original problemСумма цифр факторного

Это не домашнее задание вопрос. Я просто подумал, что кто-то может знать реальное решение этой проблемы.

Я был на конкурс программирования в 2004 году, и была эта проблема:

Учитывая п, найти сумму цифр п !. n может быть от 0 до 10000. Ограничение по времени: 1 секунда. Я думаю, что для каждого тестового набора было до 100 номеров.

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

Но был парень, который решил эту проблему примерно с 10 строками кода, и он дал бы ответ в кратчайшие сроки. Я считаю, что это было какое-то динамическое программирование или что-то вроде теории чисел. В то время нам было 16, поэтому это не должно быть «ракетной наукой».

Кто-нибудь знает, какой алгоритм он мог бы использовать?

EDIT: Извините, если я не поставил вопрос ясно. Как сказал mquander, должно быть умное решение, без bugnum, с простым кодом Pascal, пару петель, O (n) или что-то в этом роде. 1 секунда больше не является ограничением.

Я нашел here, что если n> 5, то 9 делит сумму цифр факториала. Мы также можем найти количество нулей в конце номера. Можем ли мы это использовать?

Хорошо, еще одна проблема из конкурса программирования из России. С учетом 1 < = N < = 2 000 000 000, выход N! mod (N + 1). Это как-то связано?

+1

Вы уверены, что это не был повторен сумма цифр, как 88 -> 8 + 8 = 16 -> 7? Я могу сделать это в 10 строках кода. – tom10

+0

@ tom10: Это вряд ли будет вопросом; так как решение было бы просто «если n> = 6 вернет 9, а еще вернет n-й элемент (1, 2, 6, 6, 3)». Это потребует менее 10 строк кода. :-) – ShreevatsaR

+1

@ShrevatsaR, и все остальные: да, да, я понимаю, что моя перефразировка делает этот вопрос довольно простым для большинства в этом списке, но это не плохой вопрос для 16-летнего. И учитывая, что он сидит здесь на SO без ответа в течение нескольких часов ... действительно ли исходное заявление кажется разумным? Или это Тест Путнам компьютерных наук? – tom10

ответ

2

1 секунда? Почему вы не можете просто вычислить n! и добавить цифры? Это 10000 умножений и не более нескольких десятков дополнений, которые должны занимать примерно одну миллионную долю секунды.

+3

Во многих языках нативные целые типы будут переполняться очень быстро. Кроме того, несомненно, существуют более элегантные и эффективные решения. –

+2

Конечно, но есть также много языков с собственной поддержкой бонусов. Во всяком случае, я уверен, что есть умное решение, но вопрос должен каким-то образом ошибиться в ограничениях, иначе это было бы очень просто. – mquander

+2

Даже в Python, с поддержкой родной бигнума, наивная (незамысловатая) реализация достигает глубины рекурсии через несколько секунд. По мере необходимости он не достигает 10000. –

4

Маленький быстрый скрипт python, найденный по адресу 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 
+2

Я верю, что 'lambda x, y: x * y' можно изменить на' operator.mul', и он будет быстрее, потому что он будет напрямую использовать встроенный оператор умножения, а не косвенно использовать его через лямбда. То же самое касается 'lambda x, y: x + y' и' operator.add' –

+2

'assert n> 0; sum (map (int, str (reduce (operator.mul, range (1, n + 1))))) 'немного более удобоваримый. Примечание: '+ 1'. – jfs

+0

Почему бы просто не использовать math.factorial ?? – Kugel

8

Это в Online Encyclopedia of Integer SequencesA004152. К сожалению, у него нет полезных советов о том, как его эффективно вычислять - его рецепты клена и математики принимают наивный подход.

+0

Я не понимаю, почему все ссылки копируются на его личную страницу, а не http://oeis.org/A004152 – virgo47

+0

@ virgo47 Это первый раз, когда я видел oeis.org - последний раз, когда я использовал он (и, конечно, в '09), ссылка на соединение была наиболее легко найденной. Не стесняйтесь редактировать ответ! –

3

Предположим, у вас есть большие числа (это наименьшая из ваших проблем, если предположить, что 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) операции с большими числами.

и после улучшения в редактировании только N операций на малых количествах.

НТН

+1

... как это поможет найти сумму цифр? –

+0

проблема состоит в том, чтобы вычислить число, имея несколько умножений бигнама (они экспансивны) –

6

Я атаковать вторую задачу, чтобы вычислить N! mod (N + 1), используя Wilson's theorem. Это уменьшает проблему до проверки того, является ли N простым.

+0

Итак, если N + 1 является простым, то N! mod N + 1 является N. Если N + 1 композитно и N> 4, то N! mod N + 1 равен 0. Случаи 0 <= N <= 4 легко обрабатываются отдельно. Довольно круто! – Joren

+0

О, мужик, я думаю, что где-то я столкнулся с теоремой Уилсона, но был смущен тем, как интерпретировать обозначение '(N - 1)! ≡ -1 (mod n) '. Посмотрев на него, я теперь вижу, что это означает: '(N - 1)! mod n ≡ -1 mod n' тогда и только тогда, когда 'n' является простым (Примечание:' ≡' означает «конгруэнтный»). – devuxer

+0

Итак, это означает, что если 'n = 10000', то' n + 1 = 10001', который не является простым (это '73 * 137'), поэтому' 10000! mod 10001 = 0'. Это означает, что если мы разделим '10000!' На '10001', остатка не останется. Это круто, но что теперь? Я не знаю, как сделать переход от этого к получению суммы цифр '10000! '. – devuxer

0

Давайте посмотрим. Мы знаем, что вычисление n! для любого достаточно большого количества в конечном итоге приведет к числу с большим количеством конечных нулей, которые не вносят вклад в сумму. Как насчет сбрасывания нулей по пути? Это позволило бы уменьшить размер блока?

Хм. Неа. Я только что проверил, и целочисленное переполнение по-прежнему остается большой проблемой, даже тогда ...

+0

Хорошо, быстрая проверка, похоже, показывает, что сумма цифр n! всегда делится на 3 (по крайней мере, при n <= 23). Это должно быть как-то полезно. –

+0

Он также делится на 9 –

+0

Около 7% цифр заканчиваются 0 с. Это помогает игнорировать их, но не так много. –

0

Вы должны вычислить жир.

1 * 2 * 3 * 4 * 5 = 120.

Если вы хотите, чтобы вычислить сумму цифр, вы можете игнорировать конечные нули.

Для 6! вы можете сделать 12 x 6 = 72 вместо 120 * 6

Для 7! вы можете использовать (72 * 7) MOD 10

EDIT.

Я написал ответ слишком быстро ...

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 ...

У нас есть много 2s и 3s ... Мы скоро переполнится :-(

Тогда вы все еще нужна библиотека для больших чисел.

Я заслуживаю downvoted!

+0

@ Luc, я прошел половину вашего мыслительного процесса. Избавилось от всех нулей. Как вы говорите, не имело большого значения. Он переполнился после 60-факториала :( – devuxer

0

Даже без целых чисел с произвольной точностью это должно быть грубым. В заявлении проблемы, с которым вы связались, самый большой фактор, который нужно будет вычислить, будет 1000 !. Это число с примерно 2500 цифрами. Так просто сделайте это:

  1. Выделить массив из 3000 байт, каждый байт представляет одну цифру в факториале. Начните со значения 1.
  2. Повторное умножение класса школы на массив, чтобы вычислить факториал.
  3. Сумма цифр.

Выполнение повторных умножений является единственным потенциально медленным шагом, но я уверен, что 1000 умножений могут быть выполнены за секунду, что является наихудшим случаем. Если нет, вы можете заранее вычислить несколько значений «вехи» и просто вставить их в свою программу.

Одна потенциальная оптимизация: Исключите конечные нули из массива, когда они появляются. Они не повлияют на ответ.

ОБОСНОВАННОЕ ПРИМЕЧАНИЕ: Я придерживаюсь подхода, связанного с программированием. Вы, вероятно, никогда не сделаете этого в профессиональной работе.

30

Я не уверен, кто все еще обращает внимание на эту тему, но здесь все равно.

Во-первых, в официальной связанной версии он должен быть 1000 факториалов, а не 10000 факториалов. Кроме того, когда эта проблема была повторно использована в другом конкурсе по программированию, ограничение времени составляло 3 секунды, а не 1 секунду. Это имеет огромное значение в том, как сложно работать, чтобы получить достаточно быстрое решение.

Во-вторых, для реальных параметров конкурса решение Питера звучит, но с одним дополнительным поворотом вы можете ускорить его в 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), где тильда означает, что вы можете бросить логарифмические факторы. Существует простое ускорение due to Karatsuba, которое не привносит сложную временную сложность, но все же улучшает его и может сэкономить еще один коэффициент 4 или около того. Чтобы использовать его, вам также необходимо разделить факториал на равные диапазоны. Вы делаете рекурсивный алгоритм тычок (k, n), которая умножает число от к до п по формуле псевдокода

prod(k,n) = prod(k,floor((k+n)/2))*prod(floor((k+n)/2)+1,n) 

Затем вы используете Карацуб, чтобы сделать большой размножению, что приводит.

Даже лучше, чем Карацуба, является алгоритмом умножения Шонхаге-Штрассена на основе Фурье. Как это бывает, оба алгоритма являются частью современных библиотек большого числа. Вычисление огромных факториалов быстро может быть важным для некоторых приложений чистой математики. Я думаю, что Шонхаге-Штрассен слишком много для конкурса на программирование.Карацуба действительно прост, и вы можете себе это представить в решении 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 цифрами.

0

другое решение с использованием 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))); 
} 
Смежные вопросы