Опишите алгоритм подсчёта числа завершающих нулей в десятичной записи факториала n! и обобщите результат на произвольную систему счисления с основанием b (включая составные основания); какие тонкости возникают при разложении основания b на простые множители и при больших n, и как реализовать вычисление эффективно?
Коротко — с формулами и практическими замечаниями. Идея. Число завершающих нулей в записи n!n!n! в системе с основанием bbb равно наибольшему ttt такого, что btb^tbt делит n!n!n!. Пусть при разложении основания b=∏p∣bpep
b=\prod_{p\mid b} p^{e_p} b=p∣b∏pep
(все простые множители ppp с их степенями epe_pep). Обозначим vp(n!)v_p(n!)vp(n!) — показатель степени простого ppp в разложении n!n!n!. Тогда vp(n!)=∑k≥1⌊npk⌋
v_p(n!)=\sum_{k\ge1}\left\lfloor\frac{n}{p^k}\right\rfloor vp(n!)=k≥1∑⌊pkn⌋
(Лежандрова формула), и число завершающих нулей Zb(n)=minp∣b⌊vp(n!)ep⌋.
Z_b(n)=\min_{p\mid b}\left\lfloor\frac{v_p(n!)}{e_p}\right\rfloor. Zb(n)=p∣bmin⌊epvp(n!)⌋. Пример (десятичная система b=10=2⋅5b=10=2\cdot5b=10=2⋅5): Z10(n)=min(v2(n!),v5(n!))=v5(n!)Z_{10}(n)=\min(v_2(n!),v_5(n!))=v_5(n!)Z10(n)=min(v2(n!),v5(n!))=v5(n!), поскольку v2(n!)≥v5(n!)v_2(n!)\ge v_5(n!)v2(n!)≥v5(n!). Для n=100n=100n=100: v5(100!)=⌊100/5⌋+⌊100/25⌋=20+4=24v_5(100!)=\lfloor100/5\rfloor+\lfloor100/25\rfloor=20+4=24v5(100!)=⌊100/5⌋+⌊100/25⌋=20+4=24. Тонкости и практические замечания - Разложение bbb на простые множители: - Если bbb невелик (напр., 64‑бит), достаточно перебора делителей до b\sqrt bb; сложность примерно O(b)O(\sqrt b)O(b). - Для больших bbb используйте быстрые алгоритмы факторизации (например, пробные делители + алгоритм Полларда ρ \rho ρ), иначе невозможно корректно получить все epe_pep. - Если в разложении окажется простой множитель p>np>np>n, то vp(n!)=0v_p(n!)=0vp(n!)=0 и итог Zb(n)=0Z_b(n)=0Zb(n)=0. - Вычисление vp(n!)v_p(n!)vp(n!) для большого nnn: - Сумма по степеням pkp^kpk ограничена тем, что pk≤np^k\le npk≤n. Число слагаемых =O(logpn)=O(\log_p n)=O(logpn). - При вычислении аккуратно увеличивать степень powerpowerpower и проверять переполнение: останавливать, когда power>npower>npower>n или при умножениях проверять power>n/ppower>n/ppower>n/p. - Размеры чисел: - vp(n!)≤nv_p(n!)\le nvp(n!)≤n, поэтому для очень больших nnn (больше 64‑бит) используйте типы с произвольной точностью или 128‑бит, если нужно. - Результат Zb(n)≤nZ_b(n)\le nZb(n)≤n. Эффективная реализация (алгоритм) 1. Проверить b≥2b\ge2b≥2. Если нет — ошибка. 2. Факторизовать bbb: получить набор пар (p,ep)(p,e_p)(p,ep). 3. Для каждой пары (p,ep)(p,e_p)(p,ep): - Вычислить vp(n!)v_p(n!)vp(n!) через vp(n!)=∑k=1∞⌊npk⌋,
v_p(n!)=\sum_{k=1}^{\infty}\left\lfloor\frac{n}{p^k}\right\rfloor, vp(n!)=k=1∑∞⌊pkn⌋,
на практике цикл: power=ppower=ppower=p; while power≤npower\le npower≤n: add ⌊n/power⌋\lfloor n/power\rfloor⌊n/power⌋; multiply powerpowerpower на ppp (осторожно с переполнением). - Вычислить ⌊vp(n!)/ep⌋\left\lfloor v_p(n!)/e_p\right\rfloor⌊vp(n!)/ep⌋. 4. Вернуть минимум этих значений: Zb(n)=min(p,ep)⌊vp(n!)ep⌋.
Z_b(n)=\min_{(p,e_p)}\left\lfloor\frac{v_p(n!)}{e_p}\right\rfloor. Zb(n)=(p,ep)min⌊epvp(n!)⌋. Сложность: факторизация bbb доминирует; для фиксированного набора простых ppp суммарная стоимость вычисления всех vp(n!)v_p(n!)vp(n!) равна O (∑p∣blogpn)=O(ω(b)logn)O\!\left(\sum_{p\mid b}\log_p n\right)=O(\omega(b)\log n)O(∑p∣blogpn)=O(ω(b)logn), где ω(b)\omega(b)ω(b) — число различных простых делителей bbb. Дополнительные замечания - При многократных запросах по разным nnn и одному bbb факторизацию bbb делайте один раз. - Для очень больших bbb или при требовании гарантированной скорости используйте оптимальные факторизаторы (Поллард ρ \rho ρ, ECM). - Если требуется не только число нулей, но и фактическое число n!n!n! в системе с основанием bbb — это гораздо сложнее и требует больших целых типов или модульных/вычислительных методов. Готов помочь с примером кода (на Python/C++), если нужно.
Идея. Число завершающих нулей в записи n!n!n! в системе с основанием bbb равно наибольшему ttt такого, что btb^tbt делит n!n!n!. Пусть при разложении основания
b=∏p∣bpep b=\prod_{p\mid b} p^{e_p}
b=p∣b∏ pep (все простые множители ppp с их степенями epe_pep ). Обозначим vp(n!)v_p(n!)vp (n!) — показатель степени простого ppp в разложении n!n!n!. Тогда
vp(n!)=∑k≥1⌊npk⌋ v_p(n!)=\sum_{k\ge1}\left\lfloor\frac{n}{p^k}\right\rfloor
vp (n!)=k≥1∑ ⌊pkn ⌋ (Лежандрова формула), и число завершающих нулей
Zb(n)=minp∣b⌊vp(n!)ep⌋. Z_b(n)=\min_{p\mid b}\left\lfloor\frac{v_p(n!)}{e_p}\right\rfloor.
Zb (n)=p∣bmin ⌊ep vp (n!) ⌋.
Пример (десятичная система b=10=2⋅5b=10=2\cdot5b=10=2⋅5): Z10(n)=min(v2(n!),v5(n!))=v5(n!)Z_{10}(n)=\min(v_2(n!),v_5(n!))=v_5(n!)Z10 (n)=min(v2 (n!),v5 (n!))=v5 (n!), поскольку v2(n!)≥v5(n!)v_2(n!)\ge v_5(n!)v2 (n!)≥v5 (n!). Для n=100n=100n=100: v5(100!)=⌊100/5⌋+⌊100/25⌋=20+4=24v_5(100!)=\lfloor100/5\rfloor+\lfloor100/25\rfloor=20+4=24v5 (100!)=⌊100/5⌋+⌊100/25⌋=20+4=24.
Тонкости и практические замечания
- Разложение bbb на простые множители:
- Если bbb невелик (напр., 64‑бит), достаточно перебора делителей до b\sqrt bb ; сложность примерно O(b)O(\sqrt b)O(b ).
- Для больших bbb используйте быстрые алгоритмы факторизации (например, пробные делители + алгоритм Полларда ρ \rho ρ), иначе невозможно корректно получить все epe_pep .
- Если в разложении окажется простой множитель p>np>np>n, то vp(n!)=0v_p(n!)=0vp (n!)=0 и итог Zb(n)=0Z_b(n)=0Zb (n)=0.
- Вычисление vp(n!)v_p(n!)vp (n!) для большого nnn:
- Сумма по степеням pkp^kpk ограничена тем, что pk≤np^k\le npk≤n. Число слагаемых =O(logpn)=O(\log_p n)=O(logp n).
- При вычислении аккуратно увеличивать степень powerpowerpower и проверять переполнение: останавливать, когда power>npower>npower>n или при умножениях проверять power>n/ppower>n/ppower>n/p.
- Размеры чисел:
- vp(n!)≤nv_p(n!)\le nvp (n!)≤n, поэтому для очень больших nnn (больше 64‑бит) используйте типы с произвольной точностью или 128‑бит, если нужно.
- Результат Zb(n)≤nZ_b(n)\le nZb (n)≤n.
Эффективная реализация (алгоритм)
1. Проверить b≥2b\ge2b≥2. Если нет — ошибка.
2. Факторизовать bbb: получить набор пар (p,ep)(p,e_p)(p,ep ).
3. Для каждой пары (p,ep)(p,e_p)(p,ep ):
- Вычислить vp(n!)v_p(n!)vp (n!) через
vp(n!)=∑k=1∞⌊npk⌋, v_p(n!)=\sum_{k=1}^{\infty}\left\lfloor\frac{n}{p^k}\right\rfloor,
vp (n!)=k=1∑∞ ⌊pkn ⌋, на практике цикл: power=ppower=ppower=p; while power≤npower\le npower≤n: add ⌊n/power⌋\lfloor n/power\rfloor⌊n/power⌋; multiply powerpowerpower на ppp (осторожно с переполнением).
- Вычислить ⌊vp(n!)/ep⌋\left\lfloor v_p(n!)/e_p\right\rfloor⌊vp (n!)/ep ⌋.
4. Вернуть минимум этих значений:
Zb(n)=min(p,ep)⌊vp(n!)ep⌋. Z_b(n)=\min_{(p,e_p)}\left\lfloor\frac{v_p(n!)}{e_p}\right\rfloor.
Zb (n)=(p,ep )min ⌊ep vp (n!) ⌋.
Сложность: факторизация bbb доминирует; для фиксированного набора простых ppp суммарная стоимость вычисления всех vp(n!)v_p(n!)vp (n!) равна O (∑p∣blogpn)=O(ω(b)logn)O\!\left(\sum_{p\mid b}\log_p n\right)=O(\omega(b)\log n)O(∑p∣b logp n)=O(ω(b)logn), где ω(b)\omega(b)ω(b) — число различных простых делителей bbb.
Дополнительные замечания
- При многократных запросах по разным nnn и одному bbb факторизацию bbb делайте один раз.
- Для очень больших bbb или при требовании гарантированной скорости используйте оптимальные факторизаторы (Поллард ρ \rho ρ, ECM).
- Если требуется не только число нулей, но и фактическое число n!n!n! в системе с основанием bbb — это гораздо сложнее и требует больших целых типов или модульных/вычислительных методов.
Готов помочь с примером кода (на Python/C++), если нужно.