Задачі на
довгу арифметику h2>
Розглянемо
досить популярну в програмуванні завдання на роботу з
"довгими" числами. Реально з "астрономічними" або
"мікроскопічними" числами доводиться стикатися не так вже й
часто. Тим не менше, вправи, що розглядаються в цій публікації, можуть
послужити хорошою тренуванням в області програмування і зайняти гідне
місце в класах з поглибленим вивченням інформатики або на гуртках з програмування.
Алгоритми, представлені нижче, записані на Turbo Pascal, версія7.0. При
бажанні або необхідності вони можуть легко бути адаптовані до будь-якої іншої
програмному середовищі. p>
Діапазон
подання цілих чисел (Integer, Word, LongInt) обмежений, про що не раз вже
говорилося (втім, для дійсних величин це зауваження теж актуально).
Тому при вирішенні завдань завжди доводиться діяти з осторогою, - як би не
допустити виникнення помилки виходу за діапазон або переповнення. Наприклад,
обчислюючи факторіал (n! = 1 * 2 * 3 * ... * n), в діапазоні подання величин типу
Integer вдасться правильно отримати тільки 7! = 5040, а в діапазоні подання
типу LongInt - 12! = 479001600. Для більших значень, звичайно, можна використовувати
дійсні типи даних, але це вже не гарантує точного результату.
Тому корисно для отримання точних значень при дії з багатозначними
числами розробити інші способи представлення таких чисел, алгоритми
виконання арифметичних та інших операцій, процедури введення та виведення
результатів і т.д. p>
Покажемо
реалізацію рішення такого роду завдань на прикладі множення одного багатозначного
числа на інше. Саме ця арифметична операція найбільш часто використовується
при вирішенні інших завдань. p>
Найбільш
природним способом представлення багатозначного числа є запис кожного
його розряду у вигляді окремого елемента лінійного масиву (або списку, де
пам'ять під цифру буде приділятися в міру потреби, в той час як в масиві
доводиться наперед задавати максимальну кількість елементів у ньому). Нехай
(для зручності подальших дій) розряди "довгого" числа при
записи в масив нумеруються з одиниці, починаючи з розряду одиниць, тобто фігура з
розряду одиниць - елемент масиву з номером один, цифра з розряду десятків - елемент
масиву з номером два і т.д. Визначимо для роботи з "довгими"
невід'ємними числами тип даних: p>
Const MNax = 2000; p>
Type
Digit = 0 .. 9; p>
DlChislo = Array [1 .. Nmax] Of Digit; p>
Для вирішення
поставленого завдання необхідно вміти виконувати наступні дії: p>
1) введення
"довгого" числа; p>
2) власне
множення двох "довгих" чисел; p>
3) висновок
"довгого" числа; p>
4) визначення
кількості цифр у записі числа. p>
Кожну з
підзадач реалізуємо у вигляді окремої підпрограми. Почнемо з введення. Ввести велике
число доцільно у вигляді рядка, а в подальшому перетворити в масив цифр.
У процедурі врахований вказаний вище спосіб розміщення "довгого" числа в
масиві, тобто з точки зору користувача число записується як би у зворотному
порядку. p>
(Процедура перетворення
довгого числа, записаного p>
у вигляді рядка, в масив цифр; мінлива OK
приймає значення True, p>
якщо в запису числа немає сторонніх символів,
відмінних від десяткових p>
цифр, інакше - false) p>
Procedure Translate (S: String; Var
A: DlChislo; Var OK: Boolean); p>
Var I: Word; p>
Begin p>
Zero (A); I
: = Length (S); OK: = True; p>
While (I
> = 1) And OK Do p>
Begin p>
If
S [I] In ['0 '.. '9'] p>
Then
A [Length (S) - I + 1]: = Ord (S [I]) - 48 p>
Else
OK: = False; I: = I - 1 p>
End p>
End; p>
У процедурі
викликається підпрограма Zero (A), призначення якої - запис нуля в кожен
розряд довгого числа. Ось текст цієї процедури: p>
(Процедура обнулення довгого числа) p>
Procedure Zero (Var A: DlChislo); p>
Var I:
Integer; p>
Begin p>
For I
: = 1 To NMax Do A [I]: = 0; p>
End; p>
Таким чином,
довге число записано в масив, де попереду (як елементи з великими
номерами) стоять незначущий нулі. При виконанні дій і виведення відповіді вони не
враховуються. p>
Зараз
розробимо функцію визначення кількості значущих цифр у записі числа,
оскільки вона буде потрібно при реалізації підпрограми множення. p>
(Функція визначення кількості цифр у
запису довгого числа) p>
Function Dlina (C: DlChislo):
Integer; p>
Var I:
Integer; p>
Begin p>
I: =
NMax; p>
While (I
> 1) And (C [I] = 0) Do I: = I - 1; p>
Dlina: =
I p>
End; p>
При її
розробці було використане таке міркування: якщо кількість не дорівнює нулю,
то кількість цифр у його записи одно номером перші цифри, відмінною від нуля,
якщо перегляд числа здійснюється від старшого розряду до молодшого. Якщо ж
довге число дорівнює нулю, то виходить, що кількість цифр у його записи одно
одній, що і було потрібно. p>
Ну і, нарешті,
головна процедура, заради якої і була зроблена вся попередня робота. При
складанні алгоритму використовується ідея множення "стовпчиком", хоча в
нашому варіанті складання виконується не після закінчення множення, а по ходу його,
тобто перемноживши чергові цифри, відразу ж додаємо результуючу цифру в
потрібний розряд і формуємо перенесення в наступний розряд. p>
(Процедура
множення довгих чисел. p>
A, B - множники, C - твір) p>
Procedure Multiplication (A, B:
DlChislo; Var C: DlChislo); p>
Var I, J:
Integer; P: Digit; VspRez: 0 .. 99; p>
Begin p>
Zero (C); p>
For I: = 1
To Dlina (A) Do (Цикл за кількістю цифр p>
в першому числі) p>
Begin p>
P: = 0; (Спочатку перенесення дорівнює
нулю) p>
For J: = 1 To Dlina (B) Do (Цикл по
кількістю цифр p>
в другому
числі) p>
Begin p>
VspRez: = A [I] * B [J] + P + C [I + J - 1]; p>
C [I + J - 1]: = VspRez Mod 10; (Чергове
значення цифри в p>
розряді I + J - 1) p>
P: = VspRez Div 10 (Перенесення в наступний розряд) p>
End; p>
C [I + J]: = P (останній перенесення може
бути відмінний від нуля, p>
запишемо його в поки що
вільний розряд) p>
End p>
End; p>
Зараз приведемо
лістинг програми цілком. p>
Program DlUmn; p>
Const NMax = 2000; p>
Type Digit = 0 .. 9; DlChislo = Array [1 .. Nmax] Of Digit; p>
Var S: String; p>
M, N, R, F
: DlChislo; p>
I, MaxF:
Word; p>
Logic: Boolean; p>
(Процедура
обнулення довгого числа) p>
Procedure Zero (Var A: DlChislo); p>
Var I: Integer; p>
Begin p>
For I: = 1
To NMax Do A [I]: = 0; p>
End; p>
(Функція
визначення кількості цифр у записі довгого числа) p>
Function Dlina (C: DlChislo): Integer; p>
Var I: Integer; p>
Begin p>
I: = NMax; p>
While (I
> 1) And (C [I] = 0) Do I: = I - 1; p>
Dlina: = I p>
End; p>
(Процедура
друку довгого числа) p>
Procedure Print (A: DlChislo); p>
Var I: Integer; p>
Begin p>
For I: =
Dlina (A) DownTo 1 Do Write (A [I]: 1); p>
WriteLn p>
End; p>
(Процедура
перетворення довгого числа в масив цифр) p>
Procedure Translate (S: String; Var A: DlChislo; p>
Var OK: Boolean); p>
Var I: Word; p>
Begin p>
Zero (A); I
: = Length (S); OK: = True; p>
While (I
> = 1) And OK Do p>
Begin p>
If S [I]
In ['0 '.. '9'] p>
Then
A [Length (S) - I + 1]: = Ord (S [I]) - 48 p>
Else OK: =
False; p>
I: = I --
1 p>
End p>
End; p>
Procedure Multiplication (A, B: DlChislo; Var C:
DlChislo); p>
Var I, J: Integer; P: Digit; VspRez: 0 .. 99; p>
Begin p>
Zero (C); p>
For I: = 1 To
Dlina (A) Do p>
Begin P: = 0; p>
For J
: = 1 To Dlina (B) Do p>
Begin p>
VspRez: = A [I] * B [J] + P + C [I + J - 1]; p>
C [I + J - 1]: = VspRez Mod 10; p>
P: = VspRez Div 10 p>
End; p>
C [I +
J]: = P p>
End p>
End; p>
(Основна
програма) p>
Begin p>
Repeat (повторюємо введення, поки число не буде
введено правильно) p>
Write ( 'Введіть перший множник:'); p>
ReadLn (S); Translate (S, M, Logic) p>
Until Logic; p>
Repeat p>
Write ( 'Введіть другий множник:'); p>
ReadLn (S); Translate (S, N, Logic) p>
Until Logic; p>
Multiplication (M, N, R); Print (R) p>
End. p>
У наведеному
лістингу Print - процедура виведення довгого числа. Надамо читачеві
самостійно розібратися в алгоритмі її роботи. p>
Повернемося до
обчислення факторіалу. Використовуючи розроблені підпрограми, визначимо,
значення факторіалу якого максимального числа можна розмістити в пам'яті при
таке подання довгих чисел. p>
Ось змінений
фрагмент основної програми, що вирішує поставлене завдання. p>
Begin p>
MaxF: =
810; p>
Zero (F); p>
F [1]: = 1; p>
For I: = 1
To MaxF Do p>
Begin p>
Str (I, S); (перетворення числа I до
строковому типу S) p>
Translate (S, M,
Logic); p>
Multiplication (F, M, F); p>
Print (F); p>
WriteLn ( 'Факторіал числа', I: 4, 'містить', Dlina (F), 'цифр .') p>
End p>
End. p>
Розрахунки
показали, що можна обчислювати факторіали до значення 810! включно, в
запису якого 1999 цифр. Далі знову виникає переповнення. Розрахунки за
програмі тривають близько 5 хвилин (IBM PC з процесором Pentium-100). p>
Нижче буде
запропонований список завдань для самостійного виконання. З них, на думку
автора, найбільшу складність представляють реалізації алгоритмів поділу одного
довгого числа на інше і витяг квадратного кореня. Алгоритм добування
квадратного кореня докладно описаний у довіднику В.А. Гусєва та А.Г. Мордковіча
[7]. У деяких випадках складені програми можуть виступати як
підпрограми при розробці алгоритмів вирішення інших, більш складних (як у
прикладі з факторіали), завдань. Крім авторських задач і завдань зі списку
літератури тут наведені завдання з олімпіад школярів з програмування,
що проводилися в Пермської області в 1989-99гг. p>
Завдання для
самостійного рішення p>
Скласти
програму порівняння двох багатозначних чисел (кількість знаків у записі чисел
более20). p>
Скласти
програму, що підсумовує два натуральних багатозначних числа з кількістю знаків
более20. p>
Скласти
програму обчислення ступеня an, якщо a> MaxInt, n> 10. p>
Скласти
програму обчислення числа 264 - 1, в результаті зберегти всі цифри. p>
Скласти
програму обчислення 100!. p>
Скласти програму
витягу точного квадратного кореня з n-розрядного числа (n> 40). p>
Скласти
програму обчислення точного значення n!, де n> 12. p>
Скласти
програму обчислення точного значення nn, де n> 10. p>
Скласти
програму ділення числа a на число b, якщо a, b - багатозначні числа. p>
Обчислити 100!
+ 2100. p>
Обчислити 100!
- 2100. p>
Обчислити 7123.
p>
Зустрічаються чи
серед цифр числа 211213 - 1 дві поспіль йдуть дев'ятки? p>
Обчислити
2-200. p>
Скласти
програму знаходження приватного та залишку від ділення m-значного числа на
n-значне (m, n> 20). p>
З'ясувати, яке
з чисел am, bn більше і на скільки (a, b <= 40000; m, n <= 10). p>
Знайти n знаків
в десяткового запису квадратного кореня з цілого числа m (n> = 50). p>
Знайти
кількість дільників n-значний натурального числа (n> 20). p>
Обчислити
точне значення (n!)! (n> = 3). p>
Скласти
програму обчислення точного значення суми 1! + 2! + 3! + ... + N! при
n> 10. p>
Скласти
програму обчислення точного значення суми дробів p>
p>
при n> 10.
Відповідь має бути представлений у вигляді несократімой дробу P/Q, де P, Q --
натуральні числа. p>
Обчислити
точне значення (nn)! при n> = 3. p>
Скласти
програму обчислення точного значення суми перших n членів послідовності
1, k, k2, k3, ..., kn (n> MaxInt). Вказівка: використовуйте формулу суми n
членів геометричної прогресії. p>
Скласти
програму обчислення точного значення суми перших n членів послідовності
чисел, кратних даного натурального числа k (n> MaxInt). Вказівка:
використовуйте формулу суми n членів арифметичної прогресії. p>
Обчислити
точне значення суми 12 + 22 + 32 + ... + n2 (n> = 20000). p>
Обчислити
точне значення суми 1n + 2n + 3n + ... + Nn (n> = 10). p>
Знайти першу
просте число, яке більше 1011. p>
Скласти
програму обчислення точного значення многочлена anxn + an - 1xn - 1 + ... +
a1x + a0, де aiіx - цілі числа більше 1011. p>
Знайти
найбільший спільний дільник і найменше спільне кратне чисел m і n (m,
n> = 1011). p>
Перевірити,
чи є числа m і n (m, n> = 1011) взаємно простими. p>
Доведіть, що
число 219936 * (219937 - 1) є досконалим, тобто дорівнює сумі всіх своїх
дільників, крім самого себе. p>
"Обертове
число ". Написати програму, яка знаходить число, що володіє наступними
властивостями: p>
1) число
закінчується на 5; p>
2) при
примноження його на 5 утворюється нове число, яке може бути отримано з
вихідного викресленням цифри 5 на кінці і переписуванням її в початок числа. p>
33. Дана
послідовність, задана рекурентних формулою p>
an + 1 = 7an mod 2023, a1 = 1, p>
де x mod y
означає залишок від розподілу x на y. Написати програму, яка обчислює an при
1 <= n <= 1000000000000000000000. p>
Дана
послідовність p>
p>
Написати
програму, що знаходить точне значення an при 1 <= n <= 150. p>
Приклад. При n =
58 отримуємо an = 10359022039470231387111424. P>
35. Напишіть
програму перекладу багатозначного числа (з кількістю знаків більше 20) в
системи числення з основою два, вісім, шістнадцять. p>
36. Розкласти
на прості множники натуральне число з кількістю знаків більш 11. p>
37. Множення
періодичної дробу. Задана деяка позитивна правильна періодична
дріб Q і натуральне число N. Числа Q і N такі, що кількість цифр,
використовуються для їх опису, не перевершує 100. При зображенні дробу Q періодична
частина полягає в круглі дужки. p>
Потрібно
написати програму, яка визначає результат множення Q на N, тобто
неперіодичних частину і мінімальний період числа Q * N. p>
У разі
отримання результату множення у вигляді кінцевої дробу дужки опускаються. p>
Приклад роботи
правильної програми p>
Введіть
періодичну дріб: 0.1 (6) p>
Введіть
натуральне число: 2 p>
Відповідь: 0. (3) p>
Список
літератури h2>
Абрамов С.А.,
Гнездилова Г.Г., Капустина Е.Н., Селюн М.І. Завдання з програмування. М.:
Наука, 1988. p>
Олімпіади з
інформатики. Завдання й рішення. Методичні рекомендації для вчителів і
учнів шкіл. Красноярськ, 1991. p>
Пильщиків В.Н.
Збірник вправ з мови Паскаль. М.: Наука, 1989. p>
Касаткин В.Н.
Інформація. Алгоритми. ЕОМ. М.: Просвещение, 1991. p>
Хонсбергер Р.
Математичні родзинки. М.: Наука, 1992. p>
Семакін І.Г.,
Шестаков А.П. Лекції з програмування. Перм: изд-во ПГУ, 1998. p>
Гусєв В.А.,
Мордковіч А.Г. Математика. Довідкові матеріали. М.: Просвещение, 1990. p>
Гладков В.П.
Курс лабораторних робіт з програмування. Перм: изд-во ПДТУ, 1998. p>
Шестаков А.П.
Завдання на довгу арифметику/ p>
Для підготовки
даної роботи були використані матеріали з сайту http://www.comp-science.narod.ru/
p>