@lil_web

Как найти последную ненулевую цифру K!?

Задачка по программированию. Помогите решить.

Маша составляет последовательность, где на K месте стоит последняя ненулевая цифра K!. Помогите ей заполнить пропуски.

Входные данные:
Первая и единственная строка содержит число K (0 ≤ K ≤ 105)

Выходные данные:
Последная ненулевая цифра K!

Пример: ввели 5, вывелось 2.
  • Вопрос задан
  • 1174 просмотра
Решения вопроса 1
AnnTHony
@AnnTHony
Интроверт
ideone

<?php

function normalize($num)
{
	while ($num % 10 == 0)
	{
		$num = intdiv($num, 10);
	}
	
	return ($num % 100);
}

$k = 50; //  k! = 30414093201713378043612608166064768844377641568960512000000000000
$result = 1;

for ($i = 2; $i <= $k; $i++)
{
	$result = normalize($result * $i);
}

echo $result % 10; //  2
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
wataru
@wataru Куратор тега Алгоритмы
Разработчик на С++, экс-олимпиадник.
Можно быстро, за log (k) подсчитать, сколько там нулей в k!

Для этого какая там степерь 2 и 5 и возьмите минимум.
Простое p войдет в k! в степени floor(k/p) + floor(k/p^2) + floor(l/p^3)...
Можно легко это реализовать рекурсивно PowerInFactorial(n, p) = n >= p ? n/p + PowerInFactorial(n/p) : 0;

Теперь вы знаете, сколько нулей в факториале. Если их все убрать, то задача становится совсем простой - найти последнюю цифру. Это можно сделать просто ведя все вычисления по модулю 10.

Т.е. перемножаете все числа от 2 до k, предворительно сокращая двойки и пятерки, сколько надо.

twos = fives = min(PowerInFactorial(k, 2), PowerInFactorial(k, 5));
ans = 1;
for ( i = 2; i <= k; i++) {
  j = i;
  while (j % 2 == 0 && twos > 0) {
    j/=2;
    twos--;
  }
  // same for fives
  ...
  ...
  ans = (ans*j)%10;
}


В итоге решение за O(k), потому что вложенные циклы вполнятся суммарно столько раз, сколько нулей в k!, а их по формуле выше, меньше O(k).
Ответ написан
Комментировать
@nafa2017
У меня ребенок учится на курсах Сириус (Алгоритмы на Python) и там дали эту задачу. Для решения построили визуализацию последней цифры на графике в Excel. Наглядно видно, где что и как повторяется. На чем повторение "сбивается" выносим в отдельный ряд, и т.п. пока не начнет повторяться все. Дальше ребенок решил сам. Сначала он написал код, а потом уже сделал описание.

1. Факториал делится на ряды:

1 * 2 * 3... * 9 * 11 * 12 * 13 ... * 19 * 21...
*10 * 20 * 30.. 90 * 110 * 120... *190 * 210...
*100 * 200 * 300.. 900 * 1100 * 1200... *1900 * 2100...
*1000 * 2000 * 3000.. 9000 * 11000 * 12000... *19000 * 21000...
*10000 * 20000 * 30000.. 90000 * 110000 * 120000... *190000 * 210000...
....

2. Расчет для ряда 1 * 2 * 3 ... :

2.1. Если заданное число меньше чем 2, результат 1.
2.2. Разделим полученый ряд на два ряда. Первый ряд состоит из чисел оканчивающихся на 2 и 5, второй из всех остальных чисел.
2.2.1 Первый ряд: 2 * 5 * 12 * 15 * 22 * 25 * 32 * 35 *...
2.2.2 Второй ряд: 1 * 3 * 4 * 6 * 7 * 8 * 9 * 11 * 13 * 14 * 16 * 17 * 18...21 * 23...94 * 96...

2.3 Сначала мы считаем последнюю цифру для второго ряда.
Начиная с 4, последняя цифра будет повторятся каждые 28 умножений (40 если прибавить кол-во пропущенных чисел - ряд из п. 2.2.1, а также ряды 10..., 100... из п. 1). Для определения цифры получим остаток от деления заданного числа на 40.

2.4. Считаем последнюю цифру для первого ряда (2.2.1)

разбиваем ряд на пары (2*5) * (12*15) * (22*25) * ...

Число 2, 12, 22, 32... можно записать как x2 аналогично и с 5, 15, 25, 35... (x5) . Сделаем преобразование
x2 * x5 = x2/2*2 * x5/5*5 = x2/2 * (2*5) * x5/5 = (x2/2) * 10 * (x5/5)
при умножении на десять последняя ненулевая цифра не поменяется. Поэтому достаточно рассчитать (x2/2) * (x5/5).

2.5. Рассчитаем x2/2. Числа 2, 12, 22, 32 ... при делении на 2 оканчиваются цифрами 1, 6, 1, 6, 1, 6, ... . Что не влияет на результат. Так как у второго ряда (п 2.2.2) в ответе четное число, а умножение четного числа на 6 и 1 даeт число с той же цифрой на конце.

2.5.1 Заметим, что каждое второе число, оканчивающеся на 6 будет с четной цифрой перед 6: 6, 26, 46 ... Таких чисел будет 1/40 от общего числа множителей.

2.6. Рассчитаем x5/5. Числа 5, 15, 25, 35 ... при делении на 5 оканчиваются цифрами 1, 3, 5, 7:
a1, b3, c5, d7, e9, f1, g3, h5, i7, j9, k1, l3... что уже меняет результат.

2.6.1. Сгруппируем их по последней цифре:
(1 * 11 * 21...) * (3 * 13 * 23 * 33...) * (5 * 15 * 15 * ... ) * ....

2.6.2. Если перемножать числа оканчивающиеся на 1 (первая скобка) то последняя цифра будет повторяться так: 1, 1, 1 на результат не повлияет, поэтому единицы не считаем.
Если перемножать числа оканчивающиеся на 3 то последняя цифра будет повторяться так: 3,9,7,1 (период 4).
Если перемножать числа оканчивающиеся на 7 то последняя цифра будет повторяться так: 7,9,3,1 (период 4)
Если перемножать числа оканчивающиеся на 9 то последняя цифра будет повторяться так: 9,1 (период 2)

Для определения итога достаточно получить остаток от деления количества таких чисел на период.

2.6.3. Чисел, оканчивающихся на 5 (3я скобка в п. 2.6.1) будет 1/50 от общего числа множителей. Обозначим их a5, b5, c5 ... и сделаем преобразование:
a5 * b5 * c5 * ... = (a5 / 5 * 5) * (b5 / 5 * 5) * (c5 / 5 * 5) *...= (a5 /5 * b5 /5 * c5/5 * d5/5 *) ... * (5 * 5 * 5 * 5 * ...) =

2.6.4. Каждую свободную пятёрку из этого выражения (последняя скобка (5 * 5 * 5...)) умножим на число x6 (которое образовалось в п. 2.5.1 и которое не учитывалось ранее. Причем в этом числе перед "6" четная цифра). Такое произведение оканчивается цифрой 3. (Пятерок (1/50) не больше, чем шестерок (1/40)). Итог по всей скобке вычисляется аналогично 2.6.2.

2.6.5. У первой скобки 2.6.3 (a5 /5 * b5 /5 * c5/5 * d5/5 * ...) последние цифры будут тоже повторятся 1, 3, 5, 7, 9... и расчет выполняется аналогично 2.6. Общее число пятерок (см. п. 2.6.4) в итоге будет 1/50 + 1/250 + 1/1250 + .. .от общего числа множителей, что не больше, чем шестерок (1/40, п. 2.6.1).

2.7. Если заданное число оканчивается на цифру между 2 и 4 (включительно). Это означает что в ряду 2.2.1 есть непарная x2. Полученый ответ умножаем на два.

3. Расчет для остальных рядов из п. 1 аналогичен, т.к. последние нули на результат не влияют и их можно просто отбросить.

4. После расчетов для всех рядов (п. 1) перемножаем результаты.

5. Так как расчет расчет рядов (п. 1) не зависит друг от друга, то программу можно сделать многопроцесорной.

Сложность алгоритма. В программе 2 цикла - внешний (по рядам п. 1) и внутренний - по степеням 5 (п. 2.6), оба зависят от log(n). Таким образом, для небольших чисел O(log(n)^2). Небольшие - это для которых сложность арифметических операций O(1). Такое число в исходном вопросе.

Начиная с версии 3 в Python целые числа имеют неограниченную разрядность. Арифметические операции с большими целыми числами имеют сложность O(log(n)), таким образом для больших чисел сложность алгоритма O(log(n)^3).

На курсах Сириус ограничения: число до 10^8 и время до 2 сек. Привиденный ниже код (многопроцессорный вариант) на i7-12700 (8P + 4E ядер, 20 потоков) за 2 сек рассчитывает числа до 10^2400 (Python 3.12.4 Win64).

Код для Python 3 под спойлерами
Однопроцессорный вариант

# Last non-zero decimal digit of factorial
# Single processor version
# Code by Vasilii Belousov, 5th grade 
# Engineering and Technical School named after P.R.Popovich,
# Moscow, Russia

b = []
v = {}

def init():
    global b
    global v

    m = 3
    for i in range(4, 44) :
        r = i % 10
        if r in (0, 2, 5):
            b.append(m)
            continue

        m *= i
        m = m % 10
        b.append(m)

    v = {3: [3,9,7,1],7:[7,9,3,1],9:[9,1]}

def ost(a):

    if a > 3 :
        c = (a-4)%40

        c = b[c]

    else:
        if a<3:
            c=1
        else:
            c=3

    ad = a%10
    if ad >1 and ad<5:
        c*= 2

    return c

def enddigit(a,ch):
    g = a//10
    if a%10 >= 5:
        g +=1

    c=0
    posle = ch//2
    while g > posle :
        v = g//5
        if g%5 > posle:
            v +=1 

        c += v
        if g%5 >= 3:
            g+=5

        g = g//5
        
    return c

def pnnc(ch,colvo):
    c = v[ch][colvo%len(v[ch])-1]
    return c

def dlya5(a):

    c1 = enddigit(a,3)
    co = 1
    for v in range(5,10,2):
        c2 = enddigit(a,v)
        if v == 5:
            c2+=c1
            v = 3

        c = pnnc(v,c2)
        co = (co *c)%10

    return co


def main():
    a = int(input())
    init()
    c = 1
    while a >1:
        c = c * ost(a)*dlya5(a)%10
        a = a//10

    print(c)

main()


Многопроцессорный вариант

# Last non-zero decimal digit of factorial
# Multiprocessing version
# Code by Vasilii Belousov, 5th grade 
# Engineering and Technical School named after P.R.Popovich,
# Moscow, Russia

from multiprocessing import Pool

def ost(a):
    b = (2, 2, 2, 4, 2, 8, 8, 8, 8, 4, 6, 6, 6, 2, 6, 4, 4, 4, 4, 2, 8, 8, 8, 6, 8, 2, 2, 2, 2, 6, 4, 4, 4, 8, 4, 6, 6, 6, 6, 8)

    if a > 3 :
        c = (a-4)%40

        c = b[c]

    else:
        if a<3:
            c=1
        else:
            c=3

    ad = a%10
    if ad >1 and ad<5:
        c*= 2

    return c

def generator_c(a):
    while a>1:
        yield a
        a = a//10

    return

def enddigit(a,ch):
    g = a//10
    if a%10 >= 5:
        g +=1

    c=0
    posle = ch//2
    while g > posle :
        v = g//5
        if g%5 > posle:
            v +=1 

        c += v
        if g%5 >= 3:
            g+=5

        g = g//5
        
    return c

def pnnc(ch,colvo):
    v = {3: [3,9,7,1],7:[7,9,3,1],9:[9,1]}
    c = v[ch][colvo%len(v[ch])-1]
    return c

def dlya5(a):
    c1 = enddigit(a,3)
    co = 1
    for v in range(5,10,2):
        c2 = enddigit(a,v)
        if v == 5:
            c2+=c1
            v = 3

        c = pnnc(v,c2)
        co = (co *c)%10

    return co

def f(x):
    return ost(x)*dlya5(x)

def main():
    a = int(input())
    p = Pool()
    c=1
    for x in p.imap_unordered(f,generator_c(a)):
        c = c*x%10

    print(c)

if __name__ == '__main__':
    main()

Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы