Дагене в а , григас г к , аугутис к ф 100 задач по программированию 1993

Page 1

ВЛДАГЕНЕ ПК.ГРИГАС кЖ аугутис

ЗАДАЧ

ПО ПРОГРАММИРОВАНИЮ


ББК 32.973-01 Д 14

Рецензент: ведущий научный сотрудник Института программных систем АН России доктор педагогических н аук Ю. А. П ервин

Д14

Д аген е В. А. и др. 100 задач по программированию: Кн. для учащ ихся: Пер. с лит. / В. А. Д агене, Г. К. Григас, К. Ф. Аугутис.— М.: Просвещение, 1993.— 255 с.: ил.— ISBN 5-09-003864-3. В книге рассмотрены конкретные задачи по программированию. Все задачи интересны или своей формулировкой, или неожиданным результатом, или алгоритмом решения. Программы написаны на языке Паскаль. Книга будет интересна всем, кто ж ел а е т практически, по при­ мерам, научиться составлять программы разнообразных задач и ре­ шать их при помощи ЭВМ.

„ 4306020000— 330 — —— 34— 92, инф. письмо — 92 Д ------м 1 0 3 (0 3 )— 93 v ISBN 5-09-003864-3

©

m Б Б К 32.973-01

L eldykla «Sviesa», 1986

© Д аген е 1$. А. и другие. Перерод Шмелева Д. Д ., 1993


П РЕД И СЛО ВИ Е

Чтобы стать программистом, необходимо преодолеть про­ пасть, . разделяющую математическую за д а ч у и программу, т. е. уметь находить решение каж дой задачи и вы р аж ать его на язы ке программирования. А научить этому может только практика, поэтому необходимо самому составить мно­ го программ и разобрать очень много программ, составлен­ ных другими. Мы полагаем, что приведенные в этой книге программы о к аж утся д ля читателя полезными. Задачи подобраны разно­ образные. Д л я многих из них мы заимствовали идеи из по­ пулярных книг по математике, часть составили в процессе работы с учащимися Литовской заочной школы молодых программистов. Все программы написаны на язы ке П аскаль. Мы с т а р а ­ лись подобрать программы таким образом, чтобы они были понятны начинающему программисту, зн акомящ емуся с основами программирования и с языком П аскаль. Мы д у ­ маем, что приводимые программы помогут читателю найти ключ к решению многих других задач, здесь не разбираемых. Задачи излагаю тся таким образом, чтобы их можно было чи­ тать в произвольном порядке. Тесно связанные м еж ду собой чадачи мы старались д ать рядом. В книге есть и такие программы, которые сл у ж а т для решения ранее решенных задач или задач, которые легко решаются без помощи ЭВМ. Подобные программы приво­ д ятс я потому, что в них есть интересные моменты (ориги­ нальный способ решения, алгоритм, употребление разно­ образных конструкций язы ка программирования), что может пригодиться и д л я решения других задач. Почти после каждой задачи приводятся зад ан и я для с а ­ мостоятельной работы. В конце, книги (в разделе «Заклю чение») содержится краткое обсуждение практических вопросов, касающихся выполнения программы на ЭВМ.


Приведенный в книге СПИСОК Литературы включает не только цитируемые, но и другие книги, которые пригодятся читателям, заинтересованным в более широком и глубоком знакомстве с различными вопросами программирования. За замечания и предложения авторы благодарят рецен­ зента книги доцента Вильнюсского университета В ладаса Тумасониса, сотрудника университета Р имантаса Д аги са, учащихся Литовской заочной школы молодых программистов Видаса Стаугайтиса и Айдаса Ж андариса (они читали рукопись, будучи д есятиклассни кам и ). Особо ценные замечания, советы и предложения авторы получили от учащегося Литовской заочной школы молодых программистов, т а к ж е десятиклассника Витолиса Бендинскаса. Авторы благодарны т а к ж е сотрудникам Викторасу Д агису и Зите Куралавичюте за выполнение приводимых в книге программ на вычислительной машине'. Д л я решения одной и той ж е задачи можно составить много программ. Мы старались составить и здесь представить лучшие (те, которые короче, яснее, естественнее отражаю т суть зад ач и ). Однако д ум аем , что и эти программы не самые совершенные. Будем благодарны читателям за замечания, новые решения, а т а к ж е интересные задачи. Наш адрес: Л и т­ ва, г. Вильнюс, ул. Акадёмиёс, 4, Институт математики и информатики.

6


ВВЕДЕНИЕ Д л я того чтобы решить зад ачу, мы должны прежде всего знать, что дано (исходные данны е), а т а к ж е формулировку задачи (условие.) Решив задачу,, мы получаем то, что тре­ буется по условию,— результат. Если з а д а ч а решается при помощи электронно-вычисли­ тельной машины (Э В М ), то работа раздел яется на две части. Человек (программист) .пишет программу, а машина выполняет эту программу и исходя из предъявленных ей ис­ ходных данных получает результат. Все решение задачи можно изобразить при помощи схемы, приведенной на ри­ сунке 1. Из схемы видно, что результат работы ЭВМ и я в л я е т ­ ся результатом решения задачи. А результат работы про­ гр а м м и с т а — программа. С оздавая программу, он решает задачу по программированию, а машина, выполняя програм­ м у,— зад ач у на вычисление. Д л я одной и той ж е задачи можно составить очень много жвивалентных программ, выполняя которые ЭВМ будет получать один и тот ж е правильный результат. Какую же программу выбрать, какую считать самой лучшей? Из схемы (см. рис. 1) можно сделать вывод, что програм­ ма пишется для машины, т. е. программу пишет (составляет) человек, а читает (использует) ее машина. Так полагали прежде. От программиста не требовалось, чтобы он раскры ­ вал содержание программы. Достаточно было, чтобы машина Исходные данные

Рис. 1. Схема решения задачи. 7


в соответствии с этой программой решила задачу. Крите­ рии оценки программы были чисто «машинными». Лучшей программой считалась та, которая экономно использовала ресурсы ЭВМ : время и память. Поскольку самые первые ЭВМ производили вычисления достаточно медленно и обладали небольшим объемом памяти, то эти критерии были действи­ тельно очень важными. С течением времени положение изменилось. Выросли быстродействие и объем памяти ЭВМ. Поэтому экономич­ ность программы утратила решающее значение. С другой стороны, практика показала, что машина не единственный читатель программы. Очень часто ее читает и разбирает че­ ловек. Тем самым он знакомится с идеями и опытом других программистов, учится программировать, самостоятельно составлять программы: ведь не стоит изобретать велосипед. Кроме того, при том или ином видоизменении задачи легче модифицировать старую программу, нежели создавать новую. Приходится читать и свои собственные программы. Чаще всего их усовершенствованием и заним ается сам автор. Когда программа только что написана и еще остается свежей в памяти, читать ее совсем легко. Однако с течением времени она забы вается. Во всех упомянутых случаях необходимо вникать в смысл программы. Поэтому она долж на быть на­ писана ясно и понятно. Таким образом, появляется новый критерий оценки программы — степень ее ясности. Приведенные в книге тексты программ — это результаты решения зад ач по программированию, аналогичные ответам математических задач. А д ля того чтобы получить ответ, нам часто приходится к ак следует поработать, испытать различные пути решения. Поэтому часто важ ен не только результат программирования — программа, но и способ, посредством которого нам удалось ее получить. Это осо­ бенно интересно д л я тех, кто сам собирается составлять программы. Пути составления многих программ в книге не описы­ ваются во всех подробностях. Это заняло бы немало места и было бы скучно повторно писать одни и те ж е поясне­ ния, поскольку почти везде применяется одинаковая мето­ дика программирования. Большая за д а ч а обычно распадается на части, к аж дую из которых можно программировать отдельно. Часто для более крупных частей пишется функция или процедура. Если функция или процедура т а к ж е о казы вается слишком сложной, то она программируется т а к ж е, к а к и вся зад ач а : разложением на меньшие части. Методика программиро­


вания детально описана в «Н ач ал ах программирования» Ш . а здесь мы только поясним ее на одном примере. Тем самым мы покажем, к а к по тексту программы можно д о га ­ даться о том, каким образом она была составлена. Например, вычислим, какое наименьшее число почтовых марок по 10, 4 и 1 к. надо наклеить на простую бандероль, нес которой представляет собой исходное данное. (П лата за посылку бандероли вычисляется следующим образом: за бандероль весом до 50 г взимается 10 к., за к а ж д ы е сле­ дующие полные или неполные 50 г — еще по 5 к.) Составим программу д л я решения этой задачи. Разобьем зад ач у на четыре следующие части: 1. Ввод исходных данных. 2. Вычисление платы за посылку бандероли. 3. Подсчет числа марок, 4. Печать результатов. Составим схему программы: program бандероль (input, output); (* вес бандероли *) v a r гр, (* плата за бандероль * коп, м\ 0, (* марки по 10 коп. *) (* марки по 4 коп. *) м4, (* марки по 1 коп. *) м 1: integer-, function плата (г р -.integer):integer-, плата за бандероль весом гр (граммов) procedure integer)-,

м арки

(K on:integer ;

var

м 10,

м4,

м \:

число марок по 10, 4 и 1 к., которыми можно в ы р а­ зить плату в коп (копейках) begin read (гр)-, коп: = плата (гр)\ марки (коп, м\0, м4, м\); печать результатов end. На схеме в законченном виде отраж ена только 1-я часть. ( )на записана при помощи одного из операторов язы ка


Паскаль — read (гр ). Три прочие части не закончены. Вместо них на схеме приведены прямоугольники с описанием их действий словами. Предполагается, что 2-я часть будет в ы ­ р аж ен а функцией* 3-я часть — процедурой (заголовки для них мы у ж е создали), а 4-я часть будет внесена непосред­ ственно в текст программы. Детализируем действия, записанные в прямоугольниках. Действия первого прямоугольника (функция п лата) записы ­ ваются так: begin п л ат а: = \0-\-((гр — 1) div 50)*5 end Действия второго прямоугольника (процедура марки)' может быть записана таким образом: :v: тоЕ begin м\ 0 : = к о п коп : = коп м4 : = коп м\ : = к о п ; end ,,

div 10 . mod 10 div 4 mod 4

,,

,

,

.,1

Действия по печати результатов совсем просты, поэтому мы не выписываем их отдельно, а непосредственно включаем в текст программы. Теперь, написав функцию п лата и про­ цедуру марки, мы получим законченную программу: pro gram бандероль (input, output)', v a r гр. (* вес бандероли *) коп, (* плата за бандероль *) м 10, ;* марки по 10 коп.*} м4, (* марки по 4 коп.*) м\: ;* марки по 1 коп.*) , г; integer-,

'' .1

function плата {zp:integer):integer\ (* плата за бандероль весом гр (граммов)*)

п л а т а : = 10 + ((гр — 1) div 50)*5 end; procedure марки (Kon:integer-, v ar ж 10, м4, м \ ’. integer) (* число марок по 10, 4 и 1 к .,* ) (* которыми можно выразить плату в коп (копейках)* begin ж 10: — коп div 10; коп: = коп mod 10; ю


мА : = коп div 4; м\ : = коп mod 4; end; begin read (гр); ко п : = п лата (гр); марки (коп, м 10, л4, ж 1); writeln ( ВЕС Б АН Д ЕРО ЛИ : , г р :5 , Г ); а»гйв/п (Ч И С Л О М А Р О К :’, л 1 0 + л 4 + л 1 :7 ); шгг/е/я (’ИЗ НИХ’, м 1 0 :6 , ’ПО 10 КОП.’); writeln (” :9, м 4 :3 , ПО 4 КОП. ); writeln ( :9 , м 1 :3 , ПО 1 КО П .) end. Вот мы и составили программу. Обратим внимание на то, что во всей книге, к а к и в приведенном примере, действия, соответствующие незапрограммированным частям, формули­ руются в сж атом виде и приводятся в прямоугольниках. 11римоугольники могут находиться в любой части программы (на месте процедур, группы операторов, д а ж е на месте условия в операторе if и т. д., если эта группа является хоть в какой-то степени более сложной и самостоятельной пи сравнению с другими частям и). Теперь представим себя на месте читателя программы, у которого перед глазам и только окончательный текст проI рам мы бандероль и который хочет выяснить, каков был путь составления программы. Прежде всего познакомимся с описаниями переменных, щголовками функций и процедур (пока мы не будем в д а ­ ваться в выполняемые ими д ей ств и я ). К ак следует разбе­ ремся в действиях основной части программы. Оператор инода данных ясен сам по себе. Д ал ее идет обращение с функции плата. Прочтем только комментарий после заIп.повка функции и, не рассм атри вая сами действия, поверим, ч го функция действительно вычисляет плату за посылку бан­ дероли. Следовательно, переменной коп приписывается зн а­ мение платы. Д ал ее — обращение к процедуре марки. Снова, прочитав комментарий после заголовка процедуры, поверим результатам процедуры. Печать результатов т а к ж е совер­ шенно очевидна. Вот мы и получили представление об общем виде програм­ мы. Мы уяснили себе именно ту часть программы, которая I,ыл а составлена прежде других. Каким образом программа составлялась дальш е, показывают функция и процедура. Разберемся теперь в них. Если бы в их составе были еще 11


какие-то функции и процедуры, то мы сначала только позна­ комились бы с их заголовками и лишь затем стали бы вни­ кать в их содержание. Тем самым, разбирая программу, мы идем тем ж е путем, каким программа была состав­ лена. Если читателя заинтересует не вся программа, а лишь какая-то её часть, он может и не разбирать всю программу. Предположим, читатель интересуется только тем, к а к плата в ы р а ж а ется в почтовых м ар ках (например, он обдумывает, как записать действия, при помощи которых можно данную сумм у выразить в монетах или банкнотах). Тогда ему д о ста­ точно разобраться в процедуре марки. Он обнаружит, что в ней отдельно записаны все интересующие его действия. Обратим внимание на то, что составитель программы одновременно может составлять какой-то небольшой блок (процедуру, функцию, группу операторов). Точно т а к ж е и читатель программы может сразу охватить взглядом неболь­ шой блок программы. Поэтому обычно стремятся программ­ ные блоки делать небольшими, чтобы можно было сразу! ж е разобраться во всем блоке. Его текст не должен превы­ шать страницы. В программах много места уделяется вводу и выводу д а н ­ ных, особенно если требуется при печати красиво располо­ жить результаты. Однако для любознательного читателя программ более интересны алгоритмы, относящиеся к с у ­ ществу задачи. Мы часто можем избеж ать углубления в детали ввода и вывода данных, составляя и д л я всей задачи вместо программы функцию или процедуру. Например, для рассматриваемой задачи вместо программы бандероль можно было бы составить процедуру, имеющую такую схему: procedure банд (ё р :integer-, v ar л*10, м4, м \ -.integer)-, (* число марок по 10, 4 и 1 к., которые нужно *) (* наклеить на бандероль весом гр (граммов)*) function п лата (г р -.integer):integer; procedure марки (ко п :integer; v a r ж 10, м4, м\ -.integer); begin (* банд *) марки (плата (гр), л* 10, м4, м\) end Программист, стремящийся полностью решить задачу, должен сам написать программу, обрамляющую такую про­ цедуру. В ней должны быть предусмотрены ввод исходных данных, обращение к процедуре и печать результатов. 12


Обратим внимание на то, что в книгах и ж ур н ал а х очень iacTO решение задачи оформлено при помощи функций I процедур, поскольку именно т а к можно в сж атом виде пек-дать существо задачи, а всякий программист сможет вклюшть эту процедуру или функцию в программу, которую он ■оставит в соответствии со своими потребностями. В данной книге мы т а к ж е часто прекращаем составление фограммы д ля той или иной задачи на этапе создания функщи или процедуры. Вообще, в программах, для которых (вод или вывод данных не относится к существу дела, мы громимся записать эти действия к а к можно проще и лакошчнее. Мы не будем программировать и контроль исходных 1.1ппых. Например, в программе бандероль входным данным может быть любое целое (в том числе отрицательное) чисKI, а вес бандероли должен принадлежать некоторому интерt.i.iiy (скаж ем , от 1 г до 3 к г). Проще всего проверить при­ надлежность исходных данных указанн ому интервалу, если переменные, соответствующие данным, описываются при помощи типа отрезка. Например: v a r гр : 1 .. 3000 Каким образом будет реагировать Э В М на недопустимые манные и к ак она об этом проинформирует пользователя, замисит от конкретной ЭВМ и ее транслятора. М е ж д у тем c h ­ in ация будет значительно более ясной д ля пользователя, I I in ЭВМ напечатает сообщение об ошибке исходя из услопия задачи. Д л я того чтобы сделать это, надо записать и программе проверку значений переменной гр, а изменять |е описание не надо. Например: ii ,■/> -3000 then write ( ВЕС БАНДЕРОЛИ ПРЕВЫШАЕТ НОРМУ’) else if гр < С = 0 then write (’ВЕСЫ Н ЕИ СП РА ВН Ы ) else ■ « , вычисление и печать результатов Как видим, исчерпывающий анализ исходных данных и печать сообщений об ошибках занимают немало места. По­ т о м у мы не включаем эти действия в программы, хотя они пчеиь важны , особенно если программой пользуются многие люди.


1. Факториал

Функция вычисления факториала хорошо известна про­ граммистам. Она приводится почти в каж дой книге по про граммированйю. Поэтому с нее мы и начнем. Факториалом числа п назы вается произведение 4ncej от 1 до п включительно: п\ = 1 - 2 - 3 - ... -п. Кроме того, принимается, что 0! = 1. Факториал определяется еще и таким образом: /0! = 1, \п\ = п-(п — 1)!, 1. Это рекурсивное определение. Опираясь на него, легко написать рекурсивную функцию: function f (п '.integer)'.integer; (* факториал числа п *) begin if //— 0 then f : — 1 else f : = n*f(n — 1) ! end f.a г о. .. .1 Факториал можно вычислить и посредством нерекурсив ной функции. Она могла бы выглядеть так: function f (n :in t e g e r ) :in t e g e r ; (* факториал числа п *) v a r ff, i : integer; begin //: = !; for i : — 1 to я do i-= ff

end Эту функцию мы включим в программу, которая печа тает первые десять натуральных чисел и их факториалы 14


program факториалы (output ); v ar k '.integer-, function f ( n : in te g e r): integer; v ar ff, i : integer; begin f f - = 1; for i : = 1 to re do ff:= i* ff; f := ff end;

begin for k : = 1 to 10 do w r i t e l n (k, f(k))

(Mid.

: :‘

Выполнив данную программу, ЭВМ напечатала бы такие Iи :»ультаты: 1 1 . i ■ii - ; 2 2 ,040': 6 3 ■jo ■f-Hqot, 24 4 120 5 -- ;\:-'iv== 720 6 5040 7 40320 8 ,hxi d : i 362880 9 3628800 10 При внимательном рассмотрении можно заметить, что ил программа нерациональна: факториал каж до го числа вычисляется заново с самого начала. Этих повторных вы ­ числений следовало бы избегать, особенно когда важно беречь машинное время. Перенеся действия функции в основ­ ную часть программы и соединив их с печатью, мы получили (ц,1 значительно более быстродействующую программу: program факториалы (output); й-1 \ var ff, i'.integer; begin //: = i ; . for /: = 1 to 10 do begin ff:= i* ff; writeln(i, ff) end end. 15


Возникает вопрос: а нельзя ли было бы сделать наобо рот — включить печать в функцию? Тогда функция вы глядела бы так: function f ( п : in te g e r) : integer; v a r //, i : i n t e g e r\ begin f f' = i; for г : = 1 to n do begin

w'riteln(i, ff) end; f-= ff end -

О бращ аясь к этой функции, мы получаем не только фа кто риал числа п (который в данном случае, возможно, и не по надобится), но и все числа от 1 до п, напечатанные вмеси с их факториалами. В таком случае говорят, что функция имеет побочный эффект (в рассматриваемом примере по­ бочным эффектом является печать). Вообще говоря, посред ством функций принято описывать такие действия, резуль­ татом которых является одно определенное значение. В языке П аскаль значение функции может быть только простым чис­ лом, скалярным, логическим, символьным значением или у к а ­ зателем. А все прочее относится к побочным продуктам. В силу сказанного, если одного значения недостаточно, обычно используют процедуру. Так, упомянутые действия по печати: факториалов лучше описать посредством процедуры: procedure фпечать (n '.in teger); v a r ff, i'.integer; begin f f ’ = i; for i : = 1 to n do begin

writeln (г, ff) end end

0 Входное данное — натуральное число а. Состав функцию f, значение которой удовлетворяло бы такой сис­ теме неравенств: lf ( a ) \ < a , \ (f(a)+ \ )\ > a. 16


2. Осторожно: maxintl Самым большим целым числом, с которым может иметь дело ЭВМ, в язы ке П аскаль яв л яется стандартная констан­ та maxint. Если диапазон чисел симметричен, то наимень­ шим числом будет — maxint. Когда при выполнении ариф­ метических операций получаем слишком большое или слиш­ ком маленькое число, создается аварийная ситуация и фик­ сируется ошибка, именуемая переполнением. В таком случае » В М чаще всего печатает сообщение о том, что имеет место аварийная ситуация, и прекращает вычисление. Конечно, польза от этого невелика, так к а к результаты остаются неиодсчитанными. 11оэтому, если грозит опасность переполнения, целесооб­ разно заранее предусмотреть в программе действия, помо­ гающие избежать этого. Чаще всего мы сталкиваемся с переполнением при вычис­ лении факториала. Если 10! можно вычислить при помощи почти всякой ЭВМ, то 20! не «ум естится» в большинстве ЭВМ. 11срестроим приведенную в зад ач е 1 программу факто­ риалы таким образом, чтобы она печатала самое большое натуральное число и его факториал, который еще может 01,т . вычислен на имеющейся электронно-вычислительной машине. program факториал (output ); (* наибольший факториал, допустимый для данной ЭВМ *) v ar г, ff'.integer; begin /: = i ; = repeat i: = i-\- 1; ff:= i* ff until ff > maxint div (г+ 1 ); («'!*(/ + 1 ) > m axint *) writeln (F (\ г :1 ,’) = ’, ff) end. ЭВМ EC, используя транслятор язы к а П аскаль, пред­ назначенный д ля обучения, напечатала следующий резуль­ тат: F( 12) = 479001600. Если при решении задачи появляются и отрицательные числа, то переполнение угр о ж ает с обеих сторон и избежать ого труднее. Составим программу, которая бы проверяла, не приведет Гаказ 133

п ^ __ ..

1

СРЕДНЯЯ

17

ШКОЛА * I v


ли суммирование двух исходных данных — целых чисел а и b — к переполнению. Если будет переполнение, то напеча­ таем соответствующее сообщение. Д л я проверки, нет ли пе­ реполнения, создадим функцию переполн : function переполн (a, b: integer): boolean;, begin if .(аг^О ) and ( b ^ 0) or ( a ^ ; 0 ) and (b ^ O ) then переполн: = false else begin a : = a b s (a); b : = a b s (b)\ if maxint — a<Cb then переполн: = true e ls e переполн: = false end end

■■

яг,-.

Подчеркнем, что в процессе проверки выражения, зн а­ чения которых не попадают в допустимый интервал [ — m ax in t ; maxint], появиться не могут. Включим созданную функцию в программу: p ro gram осторожность (input, output ); v a r a, b: = integer-, function переполн (a, b-.integer):boolean-, (* не получим ли переполнения, ск л ад ы в ая числа а и Ь*) begin (* осторожность *) re ad (а, Ь)\ if переполн (а, Ь) then write ( ИЗВИНИТЕ, ТАКИЕ БОЛЬШИЕ Ч И С Л А ’, ’СЛОЖИТЬ НЕ МОГУ’) else w rite (a-\-b) end. Здесь мы продемонстрировали, к а к избежать перепол­ нения, о тк азы в аясь от действий, результатами которых я в ­ ляются слишком большие числа. Но иногда все ж е бывает необходимо выполнить такие действия (скаж ем , мы хотим вычислить 100!). Можно сделать это, представляя (кодируя) большие чис­ л а через посредство нескольких меньших. Об этом мы будем говорить в зад ач е 47. © Составьте функцию, которая проверяла бы, не проис­ ходит ли переполнения при перемножении двух данных целых чисел. 18


© © Составьте функцию, которая проверяла бы, не про­ исходит ■ли переполнения при возведении данного числа в л-ю степень.

3. Размещения и сочетания Число размещений без повторений Акп и число сочетаний без повторений Ckn подсчитывается по таким формулам [2 0 ]: = Qk "

(n —k)\

= п ( п - \ ) ... ( n - k + 1);

п\ к\(п — k)\

Применяя функцию вычисления факториала (см. з а д а ­ чу I), мы могли бы записать подсчет числа размещений или сочетаний при помощи одного оператора присваивания: Л : = f(n ) div f(n —k) или C : = f ( n ) div (f(k)*f(n — k )), где / функция вычисления факториала. Однако факториал представляет собой быстровозрастающую (функцию, и поэтому у ж е при вычислении числителя или ш аменателя дроби может возникнуть переполнение (когы а большое число), хотя результат — число размещений или сочетаний — еще не превосходил бы максимального допустимого числа maxint. Это типичный случай, когда пе­ реполнение возникает на промежуточном этапе. Чтобы уменьшить вероятность переполнения, создадим функции, непосредственно подсчитывающие число разм е­ щений и сочетаний. Кроме того, подсчитывая число сочеп= 1. 1НИЙ, мы будем опираться на известное тождество Ск 11 (например, вместо того, чтобы вычислять С]|, жем вычислить C?g): (unction а (п , k :integer):integer-, (* размещения *) v ar а а , i:integer\ begin а а : = 1; for i : = n downto n — k-\-\ do a a : = aa*i\ a: = aa end; function с (n, k :in teg er):in teg er\ (* сочетания *) v ar cc, i : integer; begin 19


cc: = 1; if п С 2 *k then k : = n — k; for i : = n downto n — k + 1 do c c : = cc*i; for i : = \ to k do cc: = c c div i; c := c c end

При вычислении функции размещений а переполнение может возникнуть только тогда, когда конечный результат превышает maxint, а при вычислении функции сочетаний С перевыполнение может возникнуть и вследствие того, что промежуточный результат (числитель дроби) превышает maxint. Однако вероятность переполнения будет все ж е меньшей, нежели при вычислении по формуле, которая в. числителе со­ держит п\ / 0 Составьте функцию, подсчитывающую число сочет ний с повторениями [20]. Примените формулу

p k _[п-\- k —1)! n~k\ (п —1)! ' © © Составьте функцию, подсчитывающую число пе­ рестановок с повторениями [20]. Примените формулу

Р («I, п2, ..., Пк) = —— ^ ------ -, где П1 + П 2 + ... + пк = п. tl\! •tl2'. * •tlk'

4. Числа Фибоначчи В 1202 г. итальянский м атем атик Леонард Пизанский (Leonardo Pisanto, около 1170 — около 1228), известный под именем Фибоначчи (Fibonacci), предложил такую зад ачу: П ара кроликов каж ды й месяц Дает приплод — двух кро­ ликов (сам ца и с а м к у ), от которых через д ва месяца у ж е получается новый приплод. Сколько кроликов будет через год, если в начале года мы имели одну пару молодых кро­ ликов? Обратим внимание на то, что числа, соответствующие количеству кроликов, которые имеются через каж ды й месяц, составляют последовательность 1, 1, 2, 3, 5, 8, 13, 21, 34, ... 20


Каждый из членов этой последовательности'^начиная с третьего, равен сумме двух предыдущих членов. Эта .послеяонательность н азы вается рядом Фибоначчи, а ее члены — числами Фибоначчи. Числа Фибоначчи имеют много интерес­ ных свойств. С ними, например, связано т а к называемое золотое сечение. 11одчеркнем, что нет единого мнения о первых числах Фибоначчи. Одни математики начинают ряд числами 1, 1, /фугис числами 1, 2, третьи — 0, 1. Однако это не меняет существа задачи: дело в том, что правила вычисления прочих членов ряда и сами эти члены во всех случаях остаются (>/1.цп и те же. Мы будем считать, что F(\) = F(2) = 1. Обозначив п-й член ряд а Фибоначчи посредством симiiu.iia /'(«), мы получим следующую рекурсивную зависимость:

l :(n) = F (n — 1) -f- F(n — 2), п ^ З , Ж ) = 1 и F( 2 ) = 1 . При наличии этой зависимости легко создать рекурдщвпозволяющую найти п-е число Фибоначчи:

11vи> (|)упкцию,

Imiction фиб (,п ’. in te g e r ):in te g e r ; begin if ( n = 1) or (n = 2) then фиб: = 1 else фиб: = фиб ( n — 1)-\-фиб (n — 2) C iu l

ф

* К ; ; <»;■

Когда n = 1 или n = 2, получаем значение функции, в ы ­ полнив функцию один (первый) раз. Когда п = 3, выполняется вторая (e ls e ) ветвь условного оператора и значение функции находится из выражения фиб (2 ) + фиб (1). Д л я того чтобы вычислить значение вы р а­ жения, следует еще д ва раза (рекурсивно) обратиться к функции фиб. Когда п = 4, функция будет выполняться пять раз, а миди п = 5 — д евять раз (рис. 2). Таким образом, при воз­ растании значения параметра функций очень, быстро воз­ растает и число обращений к функции, а тем самым увеличиилется время вычисления. Это происходит вследствие того, ч т вторая ветвь условного оператора- содержит сразу д ва рекурсивных вызова. (Во многих других рекурсивных функ­ циях или процедурах, приведенных в этой книге, например в мдлче 1, рекурсивная ветвь содержит один вызов, а число рекурсивных вызовов чаще всего прямо пропорционально (плчению параметра.) Поэтому рекурсивная функция, в ы ­ числяющая числа Фибоначчи, часто приводится к ак н агл яд ­ ный пример неэффективности. 21


сриб (4)

сриб(З)

сриб(2)

Рис. 2.

сриб (5)

сриб(2)

сриб(1)

сриб(4)

сриб(З)

/\ фиб(З)

сриб(2) сриб(2)

qju5(1)

/ \ 1ри5(1)

фиб(2)

Создадим более эффективную функцию, не прибегая к рекурсии: function фибо ( п : integer)'.integer-, v a r fn, (*F(N )*) fn 1, (*F(N — 1)*) fn2, (*F(N — 2)*) k : integer-, begin fn 1: = 1; (* F ( _ 1 ) * ) fn : = 0; (*F(0)*) for k : = 1 to n do begin fn 2 : = f n \ ; (* продвижение *) f n l : = fn; (* вперед на один *) fn : = fn\ -\-fn2 (* член р я д а * ) end; фибо: = fn end Программа бывает более компактной, когда все члены ряда вычисляются в соответствии с одними и теми ж е прави­ лами. В ряду Фибоначчи исключение составляют первые д ва члена. Если мы хотим вычислять и их в соответствии с теми же самыми правилами, следует в функции фибо искусственно продолжить ряд влево, пополнив его д в у м я фиктивными чле­ нами: F (— 1)= 1 и F( 0) = 0. Тогда ряд примет следующий вид: 1 О 1 1 2 3 5 Фиктивные члены ряда Подлинные члены ряда При наличии этих д ву х фиктивных членов все подлинные члены ряд а вычисляются по тем ж е правилам. Обе функции, приведенные в этом разделе, предназна22


чены для того, чтобы находить n -е число Фибоначчи. Если мы хотим напечатать последовательность чисел Фибоначчи, I, с. числа Фибоначчи от 1 до n -го, можно включить в про­ грамму любую из упомянутых функций. Приведем фрагмент шкой программы: for /: = 1 to п do writeln (фибо (/)) Однако подобная программа неэкономна. В соответствии с ней числа Фибоначчи вычисляются, печатаются и... з а б ы ­ ваются. При поиске нового, большего числа приходится повторять те ж е самые действия. Поэтому программы, кото­ рые должны печатать следующие друг за другом члены ряда, и и .пп бы использовать процедуру, не только вычисляющую ирои (вольный член ряда, но и печатающую его. Создадим такую процедуру: procedure фибон (n'.ititeger); var fn, (*F(N )*) fn 1, (*F(N — 1)*) fn2, ( * F ( N - 2)*) k : integer-, begin fn I : = 1 ; (*F(— 1)*) f n 0 \ (*F( 0)*) for k : = 1 to n do begin fn 2 : = fn 1; (* продвижение *) fn 1: = f n ; (* вперед на один *) fn : = f n \ Jr f n 2 \ (* член ряда *) writeln (fn) end I'lld ЗП:. 15 этой процедуре прежде всего вычисляются фиктивные ч |сны ряда. Если превратить эти члены в параметры, то по Iучим процедуру, которая печатает п членов ряда, идущих .1 двумя данными членами. Модифицировать, таким обраHIM, процедуру фибон очень просто — надо только перемен­ ные fn 1 и fn превратить в параметры и не приписывать им начальных значений. Приведем более интересную рекурсивную процедуру: pmcedure фибоначчи (п, fn 1, fn -.integer)-, (* печать п членов ряда Фибоначчи, *) (* следующих з а д ву м я данными членами fn 1, fn *) begin if / г> 0 then 23


begin writelnQn 1 +/n); фибоначчи(п— 1, fn, fn\-\-fn) end end Если мы захотим напечатать п первых членов ряд а Фибо­ наччи, то должны будем прибегнуть к этой процедуре, у к а ­ зав в качестве параметров д ва фиктивных члена: Фибоначчи (п, 1, 0). Если необходимо напечатать десять членов ряд а Фибо­ наччи, начиная с шестого, следует написать обращение: фибоначчи (10, 3, 5). Если д в а других параметра не были бы соседними чле­ нами ряда Фибоначчи, то процедура напечатала бы не после­ довательность чисел Фибоначчи, а некоторую другую после­ довательность, члены которой вычисляются в соответствии .с тем ж е правилом, что и члены ряда Фибоначчи. © Составьте программу, позволяющую найти все числа Фибоначчи, меньшие данного числа. © © Интересно представить ряд Фибоначчи графи­ чески. Например: * * **

Составьте программу, которая бы печатала ряд Фибонач­ чи в виде звездочек. Строки печатаются до тех пор, пока число звездочек ум ещ ается в строке.

5. Суммы рядов Многие из математических величин или значений функ­ ций могут быть выражены к а к суммы бесконечных после­ довательностей. Например: е = 1 + — + — + — -I-... -I—L 1!

' 2!

'

— = \— L + J — 4

24

3

- 5

3!

.

' л!

~

+ ... + (- » Г . 1 _ Ч

7 п

~

2л- 1

'


Чем больше членов ряда участвует в сложении, тем (шлее точным получается искомое значение. ('.оставим программу вычисления е — основания н ату­ рального логарифма: program логарифм (output ); v ar п : integer-, (* число с л а га е м ы х * ) х\ (* слагаемое *) (‘ •.real-, begin

х: 1.0; е: =х\ lor п : — 1 to 10 do begin

х : =х/п; с: = e - j - x ; w r i t e l n i n , е : 12: 7)

end end.

Выполнив эту программу, ЭВМ напечатала такие ре«ультаты: 1 2.0000000 2 2.5000000 2.6666667 3 4 2.7083333 2.7166667 5 6 2.7180556 7 2.7182540 2.7182788 8 2.7182815 9 2.7182818 10 1мачеиие числа е: г =2.718281828... Мы видим, что при увеличении числа членов последоваи т.пости получающаяся сумма приближается к значению е. Г а шость полученной суммы ряда и действительной суммы п.| пинается погрешностью сложения. Эту погрешность можно .....пить различными способами, однако большинство из них пн гаточно сложны. Встречаются последовательности, для |шорых можно установить связь м еж ду значениями последiii'i о из склады ваем ы х членов и величиной погрешности. 11-ному чаще всего оценивается я-й член: если он достаИ1ЧИО мал, т. е. меньше некоторого данного числа е, то счи| н-тгя, что найденная сумма удовлетворяет требованиям. ( .оздадим функцию для нахождения значения числа л, ■I тады вая члены ряда, приведенного в начале задачи: 25


function пи (эпсилон:r e a l) :r e a l; v ar с у м м а : real; z :in te g e r ; (* знаменатель *). begin с у м м а : ==0; г : = 1; repeat с у м м а: = с у м м а + 1/z; if z > 0 then z : = — ( г + 2) else z : = — (z — 2) until 4 * a b s(\ / z )< эпсилон; п и : = сумма*4 end © Составьте функции д л я нахождения с указанной точ­ ностью числа л путем сложения членов следующих рядов:

3;

+

\П— 53

T-

(2n — l)3

© © Составьте функции для вычисления с указанной точностью значений тригонометрических функций путем е л о -. жения членов следующих рядов: a) sin x

26


(i

Ио шсдение в квадрат без операции умножения К падрат любого натурального числа п равен сумме га пер-

пых почетных чисел:

I•

I : +з I | :$ + 5 I | л | Г, 1-7 I | : i + 5 + 7 + 8«.

+ Г

( )i инимиаясь на данном свойстве, составим программу, ■и I mi «л я к 111i.yк» напечатать к вадраты натуральных чисел от 1 до к |Н п|ч am кп ш )р аты (in p u t, o utp ut)',

vtiI ii, псч, ко,

(* исходное данное *) (* нечетное число *) (* ква д р а т *)

I,".Integer-,

ц In rinid(n)',

nt>4;

I;

к#! - 0 ;

foi : I to « do begin Kn: = /се + неч\ w r ite ln (k , кв)\

нен: =неч-\- 2

cud

Г- :[.n-

«'•id Нискольку мы не применяем умножения, это свойство 'tin | и было особенно важ ны м д ля вычисления на старых, ■- I I ромоханических вычислительных машинах (например, mi Iаб ул ято р а х ), т а к к а к на них операция умножения не ыип.пиялась или выполнялась очень медленно. '■> Куб любого натурального числа п равен сумме п не> <т ы -, чисел, следующих по порядку за числами, сумма ко|'• 1*1.1х составила куб числа га— 1: I' 1 3+ 5 7 + 9+11 I' 13+ 15+ 17+ 19 2 1 + 2 3 + 25 + 27 + 29 27


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

7. Извлечение

корня

из

действительных

чисе.»

Известно много различных методов извлечения корня В программировании удобнее всего пользоваться универ сальным методом, т. е. таким методом, в соответствии с ко торым одни и те же, хотя бы и более продолжительные вычисления выполняются при любых исходных данных. Та ким методом яв л яется метод итерации. Корень с н атураль­ ным основанием

у = а\[х, х ^ О , где т — натуральное число, извлекается по формуле Уп = — ( { т —

i

т \

)

y

-

i

.

+

< г,' >

Пояснимj o k h m образом по этой формуле можно получить корень у = °фс. Сначала примем, что корень равен произволь­ ному числу (например, уо = 1; доказано, что получающе­ еся в результате значение корня не зависит от того, какое значение у 0 мы избрали в качестве исходного), вычисляем новое значение корня у\, затем опять новое значение кор­ ня у 2 и т. д. К аж дое новое значение у п оказы вается все ближе к подлинному значению корня. Когда разность м е ж ­ д у значением у п и предыдущим значением г/„_| становится весьма малой — меньшей, нежели допустимая погрешность, принимаем, что полученное значение у п является корнем, и вычисление заканчивается. Приведем ряд примеров, показывающих, к а к и звлекает­ ся кубический корень из 125 при различных исходных зн а­ чениях:

у 0= х = 125.0; У\ = 8 3 .3 ; 1/2= 55.6; </з= 37.1; </4= 24.7; i/s = 16.6;

£/о= 1-0; г/i = 42.3; f/г = 28.2; г/3 = 18.9; г/4 = 12.7; г/5 = 8.7;

г/о= У1 = г/2 = г/з = г/4 = г/5 =

10.0 7.1; 5.6; 5.1; 5.0; 5.0.

Написав у у вместо у п и у вместо у п-\ (где п = 1, 2 ...), сос­ тавим следующий фрагмент программы извлечения кор­ ня m -й степени из неотрицательного числа х: 28


hi :

1.0; (н исходное значение корн я*)

ореа!

'///; ////: (И/» \) / т -\-х '/ с т е п е н ь ( у , т — \ ) / т m ill a h '.I i / ii ;/)• э п с и л о н ; ,i чи>ш>:

hi /

I/им 1.1 iI'th'Hb (у , m) — функция, которая возводит число у I I- ■п т - т . , ;i эпсилон — допустимая погрешность при милсчсНИи корня. Hiii н|i.iии. 11.пый на первый в згл я д фрагмент программы Ф и при . i II не очень хорош. И звлекая корень более высокой 1' п' пи п. 1,п<> получить очень большой результат функ"'II I irnn/h Например, если мы будем извлекать ко|||. 10000.0, т. е. выполним программу при т = 100 ■ * 101100.0, то при первом прохождении цикла получим |/ - 100 При повторном прохождении цикла необходимо ... возвести в 99-ю степень. Хотя интервал дей• I им и и.in,|.\ чисел в вычислительной машине весьма велик, Ын п " I- I:iином случае мы получим значение, не попадаю...........I hoi интервал, т. ё. произойдет переполнение. Это ......... |пи неестественным: если число, из которого мы извле.................. находится в допустимом интервале, то и значеme I "спи будет находиться в том ж е самом интервале. П олом у для пользователя данной программы будет совер......И" ипюпятно, почему машина прекратила вычисления in I, in ишс переполнения, тогда к‘а к исходное число не очень IlfVIHKO. 111 о г11,i п.чбежать переполнения, не будем возводить чис I" ни и степень; вместо этого будем много раз делить чис>1(1 I 1111 уу. ■ . " Ч 1 о| |,|иим функцию извлечения корня т -й степени (где натуральное число) из неотрицательного числа х с 11hi in ic I ыо до 10- 5 : f и 1111Ion корень (x\real\ m : in t e g e r ) :r e a l; ■ корень m -й степени из x *) const эп силон = \Е — 5; vai /у, у у , w, z, m m :r e a l; k : integer; Ill'IjlU

mr. 1.0; (* исходное значение корня *) т ш : —( т — 1) / т ; z : = x / m ;

repeat !Г -= у у ;

к : — l;

29


w := z ;

while ( £ < m ) and ( w ^ - эпсилон) do begin w := w / y y ; k := k + \ end; у у : = y *m m -\ -w until ab s(y у —y)<L эпсилон; корень: = y y end

Обратим внимание на д ва момента: 1. Формулу извлечения корня мы разбили на две чаеп Одну часть действий мы вынесли из цикла, а другую част оставили в цикле. Это было сделано из соображений эко номии. Значения (часть значений), которые не изменяютс при прохождении цикла, достаточно вычислить один раз, д начала цикла. В цикле остаются только те значения, которы изменяются в процессе прохождения цикла. Такое преобра зование вычислений назы вается чисткой цикла. 2. Когда требуется извлечь корень более высокой степс ни, то при вычислении w : = w /yy частное может оказатьс весьма малым, еще прежде чем деление будет произведен т — 1 раз. Поэтому цикл может быть закончен раньше т. е. когда частное станет меньшим, нежели эпсилон. Эт сокращ ает время работы машины. Однако здесь есть ещ более существенный момент. Когда получаются очень малы действительные числа, может возникнуть странное на ви, явление — переполнение в сторону малых чисел. Ведь, че! меньше число, тем больше абсолютная величина его поряд ка (например, 1Е — 60, 1Е — 70, 1Е — 89 и т. д .). В резуль тате число, выражаю щ ее порядок, может не уместитьс! в отведенное д ля него место. Прерывая цикл раньше, мь избегаем этого явления. © Составьте отдельную функцию д ля нахождения зна чений кубического корня у = Ц х с заданной погрешностью i (где 8 — некоторая зад ан н ая константа). 0 © В язы ке П аскаль содержится немало стандартн функций. Упомянем следующие: In (х) — натуральный логарифм числа х; ехр (х) — ех, здесь е — 2 . 71828...— основание натураль­ ных логарифмов. Применяя тождество In X

30


MMfim.ic (функцию, позволяющую найти корень га-й сте II ИИ II I X .

у

‘Ui : .

V

Корин чл натуральных чисел i i 41 |1"|1М1П(‘ корня из н атур ал ь н о го числа не в с е гд а д а е т и м.. ч|н ни :-)тим извлечение корня из целого числа похож е | | in iiimih.пенное деление. При делении целого числа на - ми пи г, ч.и'м д в а р е з у л ь т а т а — частное и остаток. П о х о ж е ■ п n I .n -м корень г а - й степени из целого числа х , получаем и I |" |улы :па целая часть корня у и остаток г. Они

Н1Ы гпким об р азо м :

I уI I

■^

1

| л, ■// j I)"1, х > 0 , где т — н ату р ал ь н о е число.

Например, при извлечении кубического корня из 26 по■ ч и м // и л = 18, а из 27 получаем у = 3 и г = 0. ■чинимся, что корень будем извлекать только из нату1-1 IUНЫЧ чисел. . ТЭН!-1 : ith-y :мi:‘ ! | | корня из натуральных чисел можно использовать i \ /1 ч миграционную формулу, к ак и для корня из действи■ и мы - чисел (см. за д а ч у 7 ). Однако затруднительно опре(г п т , , когда-следует окончить вычисления и к ак предупре­ д и in |нчн)лнение. Поэтому корень из натуральных чисел is и 1 п ж лекать иначе. Возьмем д ва натуральных числа а • \ м ежду которыми должен быть искомый корень, т. е. iiiri'iei иоряющий неравенству:

a ^ .y < C b . нш интервал делим пополам и берем его среднее число:

ab — ia-^'b) div 2. i.!..Mi.iii корень будет в одном из п р о м еж у тко в: [а; а Ь ]

■ ,<ii>. /i|. Возникает вопрос: в каком промежутке:находится Н-к 11м Iиft корень? Проще всего проверить таким образом . Ьиуральное число, из которого извлекаем.корень га-й сте|* "П. надо разделить г а — 1 раз на среднее число промеI*Ni l l |</; Ь |, т. е. на ab. Если частное меньше чем ab, то прет, будет в промежутке [a; ab], в противном случае — • промежутке (ab; Ь). Промежуток, содержащий корень, и m i , ра |делим пополам. Так будем делить до тех пор, пока ^ I |шща интервала примут значения смежных (рядом стояiiii' I чисел. Тогда искомым корнем будет левый конец проме| .и л (так как концы промежутка подбирали так, что ! !/■ 1>). 31


Как подобрать начальный промежуток, т. е. концы пр м еж утк а? Так к а к условились корень извлекать только из нат ральных чисел ( х > 0 ) , то ясно, что а = 1. Число b возьме такое, что b = x - { - 1, потому, что, и звлекая корень перво степени (не забудем и про такую возможность), результг должен быть равен х. Составим функцию для извлечения корня любой степен из натуральных чисел: function root (х, т : integer)'.integer; (* корень m-й степени из натуральных чисел *) v a r а, b (* интервал *) ab (* одна из границ нового интервала *) хх, k '.integer; begin а : = 1; b := x - \ - 1; (* исходный интервал*) while b — а > 1 do begin ab\ = (a-\-b) div 2; (* интервал д ел и тс я*) (* пополам *) x x := x ; for k : = 1 to m — 1 do х х : = xx div ab; if x x ^ a b then a : = a b else b: = a b end; root: = a end

I Данным методом можно не только извлекать корни, Hi и решать любые алгебраические уравнения. 0 Составьте процедуру для определения целой час остатка кубического корня из натурального числа. © © Составьте функцию rootmodm д л я определена остатка корня т -й степени из натурального числа.

9. Извлечение квадратного корня из натуральных чисел

Мы сталкиваемся с квадратными корнями значительж чаще, нежели с корнями какой-либо другой степени. Поэтом; во многих язы ках программирования имеется стандартна) функция извлечения квадратного корня. В язы ке Паскал! и во множестве других языков параметром этой стандарт ной функции может быть не только действительное, но ] 32


mi 'ни но, ниримор:

.1

результатом — только действительное число,

sqrt (4.0) = 2.0; sqrt (4) = 2.0; sqrt (2 .0 )= 1.414; sqrt (2 )= 1.414. И i" л ii.i.ii.i, выраженного в действительных числах, i'\ ..... и*> |\ чп п. результат в целых числах — следует толь■ ■■............. 1робпую часть числа. Например;

trunc (sqrt (4)) = 2; trunc (sqrt (2 ) ) = 1. *I........ ми стандартные функции используются, когда к и in и mi, натуральный квадратный корень. Так к ак опе...... - н Гнтпительными числами выполняются прибли1 111и, ил in- укорены, что всегда получим верный ре,'Н,|(И I г - 111 к н о , не очень логично в задаче, в которой и исход­ ит I ннн.н , п результат — целые числа, использовать более щ ими н мепое надежный тип данных — действительные и 'III | и .1 и шлекать квадратный корень тем ж е самым • til in I каким извлекается корень т -й степени из натураль...........к 'i.i (см. зад ач у 8 ). i ipimi н м законченную функцию извлечения квадратного м|ПМ| ни iidii qroot (x :in te g e r ):in te g e r ; i* , и.шрлтный корень из натуральных чисел *) ' и ч, I/, (* интервал *) пЬ, (* одна из границ нового интервала *) \х '.integer; ГЦ|М

1; (* исходный интервал*) I. Ь: while I) а > 1 do begin (ih: (a-\-b) div 2; («и н тервал д ел и тс я*) (* пополам *) и : —х div ab; If x x ^ a b then a : = a b else b : = a b rrnl;

:

• i/i m i l = a

nd 33


Часто надо бывает найти остаток при извлечении ква ратного корня из натуральных чисел. Составим функци находящую его: function sqrootmod ( х '.in teger):in teger; (* остаток при извлечении квадратного корня из *) (* натуральных чисел *) begin sqrootmod: = x — sqr(sqroot(x)) end

,

10. Арифметический к в адр ат

Известно, что многие насекомые ориентируются по сол| цу. Предположим, что у нас есть поверхность в фори ква д р а та, поделенная на клетки. В одной из угловых клетс сидит паук, а солнце светит из противоположного у п (рис. 3 ). П аук идет по н 1----- 1— Г ~ I----- г правлению к солнцу, одной клетки в другую d может переходить тольн + + I- + + + + + через отверстия, напра ленные снизу вверх ш + + + Н- + + 4 - справа налево. М ен яя н правление движения, n a j - + -h + + + + + может пройти в против положный угол квадрат - 4 - Н - + + + + + .различными путями. - + -Ы - + + + + Записав число путе ведущих в указанную кле ~h ~h + + + + "Ь ку, получим соответс J ___ L вующую таблицу 1. Эт таблица назы вается ари<| Рис. 3. Один из возможных путей паука метический к в а д р а т [20Я к солнцу.

Z'E ± ± + + + + -

Т аб ли ц а

Арифметический к вадрат 1 1 1 1 1 1

34

1 2 3 4 5 6

1 3 6 10 15 21

1 4 10 20 35 56

1 5 15 35 70 126

1 6 21 56 126 252


11 >i •i<■111111,i мы нпдим, что каждое записанное в ней число ш и ш умм! 1и\ чигс.п: находящегося непосредственно свери блнж л Линч о слова.

I Ip- и I. ш и п арифметический к в а д р а т в виде массива, мы i" || м и 1................ .шип» процедуру нахождения значений H'Mt'iMuh квндрптп: ■'и •I

м ( тр о и л квадрата *)

п

VI»* •ч ""-" a i r пу 11 ,.п\ of integer; 1m i , i/чи п и н у |1..я| of строка; iHiu'tlitH’ lumilp (var а : квадрат); vмt I, I ; i n ; • u Ih

for

Mo

r

n

III ii (/. II: I: «II. < h l ♦Mill,

lot i ■»’ lo n do Imi /: Id i i do ini

"I*. /,:

,

do

'

' •

-

(i\i— 1, j] + a [ i , j — 1]

lb п., чип процедуру в программу и написав обращение к * и п,1 получим результат — арифметический квадрат, с ко»*|*i*tм можно будет производить любые действия, в частности - • m I ■ in арифметический к в а д р а т нужен только для того, ffiin.i п о нлпочатать, то можно составить процедуру или |«*| р 1мму. которая потребует меньше места в памяти ЭВМ . (■ I- и юм. что элементы ква д р а та вычисляются по очереди. ....... и.ш'льио, можно сразу печатать только что вычислен...........п-мент. Тогда в памяти будет храниться только одна ipni I кнлдрлта, поскольку ее элементы нужны для вычисле­ ны >лсм(‘нтов другой (нижней) строки. ( ОГ1ЛНПМ программу печати арифметического ква д р а та 11 и ром с шахматную доску: (пинии квадр аты (output); ч . н,1 /1 = 8 ; (* размер к в а д р а т а п*п клеток*) Iу |**' строка = a r r a y [1 ,.п\ of integer; var i, j:\ ..n ; ст р : строка-, •HI 111 loi /: — 1 to я do begin write (1:5); стр [г]: = 1

end; 35


w rite ln ; for i : = 2 to n do begin write (1:5); for /: = 2 to n do begin стр [/]: = стр [jj+ с т р [/' — 1]; w rite (стр [/]: 5) end; writeln end end.

11. Треугольник Паскаля Числа расположены следующим образом: 11

1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 Первый и последний члены в каж дой строке равны 1, а к аж дый из прочих членов равен -сум ме двух ближайших нахо дящ ихся сверху чисел. Такое расположение чисел называете треугольником П аскаля, хотя до Б. П аскал я (P a s c a l, 1623—^ 1662) оно было известно итальянскому м атем атику Н. Тар талье (T a rtag lia, 1500— 1557), а еще раньше этот треуголь ник был описан в работах арабских математиков. Т аб л и ц а

Треугольник П аскаля, представленный в виде массива a r r a y [1..5] of a r r a y [ — 5..5] of integer -5 1 2 3 4 5 36

0 0 0 0 0

—4 0 0 0 0 1

-3 0 0 0 1 0

-2 0 0 1 0 4

. — 1 ’0

1

2

3

4

5

0 1 0 3 0

0 1 0 3 0

0 0 1 0 4

0 0 0 1 0

0 0 0 0 1

0 0 0 0 0

1 0 2 0 6


I I !'<< i hcimn 111м /it i .жить треугольник П аскал я в виде дву( ........ и Mm I him гак. как это показано в таблице 2. Места, .........I>1.1ч m i чисел, а массиве помечаются нулями. ОпредеII I птиц мп(а пи и составим процедуру вычисления треi и нашим П йсннли: . i+ •Iи«.л<> строк треугольника П а с к а л я * ) .и .1 а ,|м и ,, и,,щц a r r a y |l..ra] of a r r a y [ — га..ra] of in te g e r ; п т hum (var /'.треугольник)', vIIi /i 11 i n, /; п., 11; рц1ц to n do Iih I : I III I ' l l l l l l '

tor

Ml

till

о

n

l\h

tin 11 /,

ml

to ra do

/I:

to /I do . H+ l to ra — 1 do -/|/~ I, j — 11+ 1 [i — 1, j + 1]

i/ii'Mfiiiu 1|Н‘угольНика П ас ка л я вычисляются очень |i.h in I ) шако составленная процедура неэкономна. Массив i ' ii ........о места в памяти, а используется она нерацио| и.ми имеется много пустых элементов, помеченных нуляш II.......пну попытаемся представить в массиве элементы i i \I и п.ппка П аскаля более экономно: к аж д у ю строку тре1п нашка начнем писать с самого начала строки массива. ■ ним случае достаточно будет вдвое меньшего массива. *м|и |слим его: Ivin- греуг — a r r a y [l..ra] of a r r a y [1 ..га] of integer ifi|n....... внимание на то, что в г'-й строке треугольника I | I I in / -/лементов, а сами элементы г'-й строки вычисля• 11 и I уммированием элементов ( i— 1)-й строки так, к ак это к и I ta lit) на рисунке 4.

•I I трока

м строка ■и

I, Схема вычисления пятой строки треугольника П аскаля. 37


Составим новую процедуру: ■ procedure п аск (var t : треуг); v ar i, j : 1..я; begin for i : = l to л do for j : = i + 1 to n do t[i, /']: = 0 ; for i : = 1 to я do t[i, 1.1: = 1; for i : = 2 to я do for j : = 2 to i do t[i, /]: = t [ i — l, j — l ] + . t [ i — l , j } end Д л я того чтобы напечатать треугольник П ас кал я , необход мо снова развести « с ж а т ы е » в массиве элементы. Составим процедуру печати: procedure печать (t: треуг); v a r г, /, s : \..п; function чсцф ( п : in te g e r):in te g e r; (* число цифр *) ... (см. зад ач у 34) begin (* печать *) s : = чсцф (t[n, п div 2 + 1 ] ) ; (* самое большое число *) (* цифр *) for i : = 1 to п do begin write ( ’ : (я — г + 1) *s); (* пробелы в н ач ал е*) (* строки *) for /: = 1 to i do write (t[i, j ] : 2 *s); writeln end end Поскольку в треугольнике П ас ка л я должны быть оста лены пробелы м еж ду числами, то для печати каж дой стро( мы выделяем вдвое больше позиций, нежели занимает сам< длинное число (число, в котором больше всего цифр). Е длина устан авл и вается функцией чсцф, описанной в зад че 34. Если на месте чисел треугольника П аскаля, которь без остатка делятся на какое-нибудь число k, напечата: символ пробела, а на месте всех остальных чисел — б уи М, то мы получим интересный симметричный узор. Сост вим процедуру, в соответствии с которой производится т к а я печать, и включим ее вместе с процедурой паск в пр грамму, печатающую узор, представленный на рисунке 38


М V М

и

м

М ММ М М М

мм им и м м м Ии м м м м м м М

М

Мм Г м

мм м м

МГ ММ

М М ММ

и

м мм

м м М 1‘ мм мм ! !' I м М М М М I I I Г I' МММММММММММ м Г I* мм i г ни мМ И и и и МИи г Г г

и

мм г м г

г м |1 |/ М М

м м

м м

МММ м м м м м м м м м м м и м м м мм мм м м к к I , I f I К М М V V. М К К М I Ml Г Г М Г М М М М М М м м м м м м м м м м м м м м м

V Г !

Гм

м м м м м м М МММ ММ м м м м м м ,м, м м м м м м м м м мм

• t >|ч1мм«чи

греугольника П а ск а л я .

l/HUI.uijlhHUK (output)-, п (* число строк треугольника П а с к а л я * ) I; (* делитель для получения узора *) tу(»« i/iri/.' a r r a y [ l . . /г] of a r r a y [l../г] of; integer;,

м i n ; I H IM

........

viii

:

i t rp e y a ;

.g ! -.i >

,

IHtucdmc п аск (var t -.треуг); vai I, /: I ..At; i «и i : for

1 to n do 1 to n do t\L /]: = 0 ; for /: = ! to л do t\i, 1]: = 1; for i : = 2 to n do for /: = 2 to i do t [i, j \ : = t [ i — 1, j — 1 ] + ■m l ; (* паск *) p im cd u re узо р ( t -.треуг; k :in te g e r ) ; v ar i, j : 1..n; tie-in

|J:

/: = = /+

/ [/ —

1,

/]

39


for г: 1 to /г do begin write ( :( « — / + 1)); (* пробелы в начале строки for j : = 1 to г do if t [i, j] mod k = 0 then write (^ ’ : 2) else write ( M ’ : 2); writeln end end; (* узор *) begin (* треугольник *) . паск (tt); узор (tt, k) end. © Составьте программу, которая сохраняла бы толь одну строку треугольника П аскал я — вычисляла бы по оч реди все строки и тотчас ж е печатала бы их.

12. Треугольник Исходные данные — три натуральных числа, вы р а ж а щих длины отрезков. Требуется определить, можно ли из эт отрезков построить треугольник. Составим логическую функцию д ля решения этой з а л а 1 Будем опираться на известное положение: из трех отрезке можно построить треугольник тогда и только тогда, коп сумма длин любых двух отрезков превосходит длину третьег function треугольник (a, b, c :in te g e r):b o o le a n ; begin треугольник: = (а -\ -Ь > с) and ( a - \ - c > b ) and end (6 + 0 0 ) Эту зад ач у можно решить и в случае, если используют исходные данные вещественного типа. Тогда во всех фуь циях целый тип следует заменить на вещественный тр Поскольку операции с действительными числами выполняю ся приближенно, то в пограничных случаях можно получи неверный результат. Например, если

\a-\-b — с \ = г (где е — весьма малое число, сравнимое с погрешностям которые допускает Э В М ), то результат функции может ок заться каким угодно. 40


и i-ttt к I i |шмму, которая определяла бы и вид пНЙЬМИМ (N /I*| I ниц,и о гречки позволяют его постро­ енной. нфншшн, раипобедренный, разносторонний, i М М н^ни I in >s 11).'ti.iiiiii”i , остроугольный. • immi ........ min m ax. . т и п при» ......... рая находила бы все возможные i , i " t i HHt>H ...... . I горим которых (а, b и с — натуральI ип )и) > ......... пюря.пи бы неравенству пин ■ а b^ .с ^ т а х . I I i

b n

i i i

н а т у р а л ь н ы е

ч и с л а

и

Мчиим | 1п|н и | I I ....... треугольника печатайте на отдель1 1 in in ■ п. I пи,Iе функцию, которая определяла бы, [НМНн *i|i и . и...... . четырех отрезков (а, Ь, с и d — н атураль­ н а ни ни * иг I ш и т . прямоугольник. •I I

I | И Ц

I Мнф.мпримы числа • \m.-hi I и I 111-111>и длин катетов а и b прямоугольного ||ци и...... 1 р......а киадрату длины гипотенузы с:

а 2 -\-Ь2 = с2. i i .....I I п т ур.мм.ных чисел, удовлетворяющих этому p a ­ id I” , mi и,ищется Пифагоровыми числами. Например, I I ii in ииони I [пфагоровыми числами, поскольку 32 + 42 = 52. . .........i i Пифагоровых чисел была известна у ж е в 11.. мн. I l iiim e 1'оворят, что строители пирамид, чтобы ■■ее |•11111• прямой угол, пользовались веревкой, разделен1. 111 и,1 Г. ’ ранных частей. С гибая ее, получали треугольник, I>•..........чнорого составляли 3, 4 и 5 частей. " I апим программу для нахождения и печати всех Пипмрмшд чисел, не превышающих 20: миопии п и ф а г п р ( o u t p u t ) - , var и , b , с, с х ' . i n t e g e r - , i ■• )■in tor a : = 1 to 20 do for b = a to 20 do begin :

cx:

=

a

*

a

+

b*b\

c: = 1; while (c * c < c x ) and ( c < 2 0 ) do с : = c + 1; 41


if c*c = cx then (* числа Пифагора *) writeln (a, b, c) end end. Тройка Пифагоровых чисел, не имеющих общих делит лей, назы вается основной. Умножив к аж до е из чисел, вх дящих в основную тройку, на какое-нибудь натуральш число, снова получим тройку Пифагоровых чисел, называ мую производной тройкой. Например: 3 4 5 основная тройка Пифагоровых чисел 6

8

10

| производные

9 12 15 ? 15 20 25 J

тройки Пифагоровых чис<

Приведенная программа пифагор печатает все тройщ и основные и производные. Если бы мы искали Пифагоров числа на большем интервале, то интереснее было бы печ; тать только основные тройки чисел. Проверить, является л тройка основной, можно, используя функцию наибольшег общего делителя нод (см. з а д а ч у 21) или функцию взаймы простых чисел взаимпрост (см. зад ач у 22). Кроме того, в программе пифагор много времени тратит ся на поиск гипотенузы треугольника — по очереди пере бираются все числа. Мы бы нашли гипотенузу прямоугольника быстрее, есл бы использовали функцию извлечения квадратного корня и натуральных чисел sqroot (см. зад ач у 9 ). При помощи названных функций составим программу, которая будет на ходить основные тройки Пифагоровых чисел, не превышаю щих m a x : program пиф (input, output); v ar a, b, с, (* кандидаты в числа Пифагора *) max-.integer; function нод (x, у '.integer): integer; (* наибольший общий делитель *) ... (см. зад ач у 21) function sqroot ( х : integer)'.integer; (* квадратный корень из натуральных чисел *) ... (см. зад ач у 9) begin (*пиф*) read (max); for а : = 1 to max do for b : = a + 1 to m ax do begin 42


с: — sqroot (а * а -\-b*b); if (oS^m ax) and (,c* c = a * a -\ -b * b ) and (* числа *) (* Пифагора *) (нод (нод (a, b), c ) = l ) (* основная *) (* тройка *) then writeln (a, b, с) end ntd,

И обеих программах мы искали Пифагоровы числа, по и |и /mi перебирая все тройки чисел и проверяя, удовлетво........ in они указанны м условиям. Можно было бы сделать Н|ни рамму более эффективной, если бы мы знали более .41, Iрый способ получать тройки чисел. Такой способ есть нее основные тройки Пифагоровых чисел можно полуIM11, по следующим формулам:

a = u - v ; Ь= I /1г it и v — взаимно простые нечетные натуральные числа II ч > v . ( 'оставим программу, используя эти формулы: |1П)({гат пифаг (input, output); var и, v, max-.integer-, Inaction взаимпрост (x, y :in te g e r ):b o o le a n ; (♦являются ли числа взаимно простыми*) ... (см. з а д а ч у 22) Ь< ц1п ( * пифаг * ) read (max); и: = 1; и: = 3; while (u*u-\-v*v) div 2 ^ m ax do begin while (u*u-\-v*v) div 2 ^ m ax do begin if взаимпрост (и, v) then writeln (u * v , (u*u — v*v) div 2; (u*u-\-v*v) div 2); u : = u -\ -2 end; v : = v-\-2; u : = v - j-2 end i-iul. 43


© Составьте программу, которая будет находить все шения уравнения

x 2Ary2 = zn (п — исходное данное) в промежутке [2; 100]. © © Треугольники, у которых длины сторон и площа представляют собой целые числа, называю тся треугольнш ми Герона. Например, таков треугольник, длины стор которого равны 13, 14 и 15, а площадь — 84. Составьте функцию, определяющую, яв л яется ли Tf угольник, длины сторон которого а, Ь, с, треугольник! Герона. © 0 © Треугольников Герона очень много. Составь программу д л я нахождения самых интересных треугольп ков Герона, у которых: а) стороны выражены соседними числами (наприм! длины сторон таких треугольников 3, 4, 5 или 13, 14, 15 б) площадь равна периметру (например, таков прял угольный треугольник, длины сторон которого 6, 8, 10). В обоих случаях ищутся треугольники, длины стор( которых находятся в указанном интервале.

14. Разрезание прямоугольника на квадраты Д ан прямоугольник, длины сторон которого а и Ь пре ставляю т собой натуральные -числа. Составим программ которая будет находить, на сколько квадратов, сторон которых выражены натуральными числами, можно разреза' данный прямоугольник, если от него каж ды й раз отрезае ся ква д р а т максимально большой площади. Например, прямоугольник 7 X 1 2 следовало бы раздели' так, как это показано на рисунке 6. Было бы хорошо, если б ЭВМ напечатала I I I - I I 'I' зультаты таким обр; зом: ДАН ПРЯМОУГОЛ НИК: 7*12 КВАДРАТЫ: 7*7 1 5*5 1

Рис. 6. Разрез прямоугольника 7 X 1 2 на квадраты. 44

2*2

2

1*1

2

ВСЕГО КВАДРАТОЕ


"ИРМИН "■ 1111 ■<>i() чтобы найти число квадрадлинную сторону) разделить на fhi.it.nitс т . и. ((п IIйннчымшн

111и1111. 1м мп

выглядит так :

f f t f f l Щ§»и>1 ч ч ы {in p ul, o u tp u t);

I иt ,* i, i* ......... . i 11'рои прямоугольника*) '. " .и i ' т.ш ее число к в а д р а т о в * ) i* ми. ....... нинаковых квадратов *) i j flit It И l e i ') in !i , , , 141iti ■ m i ПРЯМОУГОЛЬНИК: , a : 1, * , .t т . i i I1И Щ РА ТЫ : );

Ннин

b: 1);

Ими (.!•(( и b меняются м естам и *)

u

tt**u In *: »; и ! - b; Ь'.анХ fit (I, (♦(! * l) +) k и tllv b\ it — it titttd b; &iih'hi ( b : I !>, * , b : 1, k); ii

n

| It

HHlil i (i ■. 0 );

г.' I ГО К В А Д Р А Т О В :, n ; 4)

i*

> ( i ,,, pi, pi ниш более простую задачу. Составим функцию I ПН н I и-пия числа квадратов, на которые можно раз.,111 прямоугольник. I mi I mu и, функцию можно, пользуясь текстом предыдуИ пршрпммы: объявить параметрами функции исходные in 11,i( (положим, что а ^ Ь ) , а т а к ж е исключить оператор щ и ............ л ко, хорошенько подумав, можно составить нан и, ""И г простую рекурсивную функцию. Вот она: и* Iton iui ( i i , b :in te g e r ):in te g e r ; ill и It b 0 then ice: = 0 else кв :==a div 6 + ке (b, a mod b) til

45


15. Равновеликие прямоугольники Составим программу для нахождения всех прямоуголь­ ников указанной площади (площадь — исходное данное, выраженное натуральным числом), стороны которых — натуральные числа. Например, если площадь равна 12, то получим три разных прямоугольника: 1X12 2X6 3X4 Будем считать одинаковыми прямоугольники, получаю­ щиеся один из другого, если поменять ребра местами. Обозначим площадь буквой р, а стороны прямоугольни­ ка — буквами а и Ь. Приравнивая одну сторону (напри­ мер, а) к 1, 2, 3, 4 и т. д. до тех пор, пока a ^ b (b получим, разделив площадь на а ) , мы сможем получить все прямо­ угольники, площади которых равны р. На язы ке П аскаль мы записываем это так: а : = 1; Ь: = р; while a ^ b do begin if a*b = p then печатается прямоугольник со сторонами н и 4

а : — а -р 1; b : -р div а end Поскольку всегда можно образовать по крайней мере один прямоугольник указанной площади, то лучше исполь­ зовать цикл repeat. Приведем законченную программу: program прямоугольник (input, output ); v ar р, (* у к а за н н а я площадь прямоуголь­ ника *) а , Ь, (* стороны прямоугольника *) сколько : integer-, (* сколько различных равновеликих *) (* прямоугольников *). begin . read (р); сколько : = 0 ; 46


а : = 1; b : =р\ repeat if a*b = p then (* найден прямоугольник*) begin сколько : = сколько + i ; writeln (a, ’*’, b: 1) end; a : — a-\ - 1; b: — p div a until a > b \ write {сколько, ’ПРЯМОУГОЛЬНИКОВ ПЛОЩАДЬЮ’, p) end. © Исходное данное — натуральное число v. Составьте программу для нахождения всех различных прямоугольных параллелепипедов, объем которых равен v, а ребра выражены натуральными числами. Параллелепипеды, получающиеся один из другого, если поменять ребра местами, считаются одинаковыми.

16. Равновеликие треугольники Рассказы ваю т, что английская королева Виктория, при­ д я в восторг от книги Льюиса Кэрролла (C a rro ll) «Алиса в стране чудес», однажды приказала принести ей другие произведения этого автора. К ак ж е удивилась королева, когда ей вручили несколько математических трудов! О ка­ залось, что эту чудесную сказочную книгу под псевдони­ мом Л. Кэрролл написал профессор математики Оксфордско­ го университета Чарльз Л а т в и д ж Доджсон (Dodgson, 1832— 1898). В его дневнике мы находим интересную зад ач у, кото­ рую и попытаемся решить. Ч. Л. Доджсон пишет, что он тщетно трудился, пытаясь найти хотя бы три прямоуголь­ ных треугольника равной площади, у которых длины сторон были выражены натуральными числами. Составим программу для решения этой задачи (будем искать треугольники возможно меньшей площади). Следовало бы просмотреть прямоугольные треугольники, стороны которых выражены натуральными числами, и у с т а ­ новить, найдутся ли хотя бы три треугольника равной пло­ щади. Однако таких треугольников может быть много. Поэтому мы будем исследовать площадь треугольника, т. е. проверять, могут ли данную площадь иметь хотя бы три треугольника, стороны которых — натуральные числа. По­ 47


скольку площадь прямоугольного треугольника равна 1/2 а -b (где а, b — катет ы ), то удобнее исследовать удвоен­ ную площадь s = a - b . В этом случае значение S будет лю­ бым натуральным числом: S = 1, 2, 3, ... Итак, переформулированная зад ач а зв уч ал а бы так : у с т а ­ новить, можно ли представить рассматриваемое н атур ал ь ­ ное число в виде произведения двух натуральных чисел по крайней мере тремя различными способами. Кроме того, сле­ дует еще проверить, может ли к а ж д а я из пар чисел пред­ ставлять катеты удовлетворяющего условию треугольни­ ка, т. е. вы раж ен а ли гипотенуза натуральным числом. Н а ­ пример, удвоенную площадь, равную 12, можно представить в виде произведения двух чисел тремя способами: 1 X1 2 2X6 3X4 Однако в треугольниках, у которых а = 1, 6 = 12 и а = 2, Ь = 6, гипотенузы с = У 12+ 1 2 2 и с =т / 2 2 62 не являю тся натуральными числами. Таким образом, число 12 не будет представлять собой удвоенную площадь, удовлетворяющую условию задачи. Подчеркнем, что следует отбросить те треугольники, у которых катеты меняются местами. Алгоритм решения задачи очень похож на алгоритм «Равновеликие прямоугольники» (см. з а д а ч у 15). Приведем схему программы: program carro ll (output)] v a r s, (* пробная двойная площадь треугольника*) а, Ь, (* катеты треугольника *) k : integer; (* число треугольников *) begin s : =0; . mv. -о repeat .,,ц. д ж ' S ’ = S +

1;

-

|чу; .

k : = 0; :

/-■ --" " А Д 1

Чл.— ,

берутся все возможные пары катетов а и Ь, такие, что: 1) а <^Ь: 2) а * b = s; 3) гипотенуза c = ^ J a 2 Ь2 выразима посредством натуральных чисел 48


u n t il f e ^ 3 ;

печатаются ' г r . IV V кптппых X/ найденных треугольника> у ко op У д в о е н н а я площадь равна s end.

---------------

Детализируем r ; = s“ Kr „ S как ищутся

,д ействия

записанные в первом прямо-

yv ? H o "Р » “ Р " ТЬ' « с т а н т а д ь н о Т „ Ь

з

г

„E L 2 5

»

фу-

т— г * ОиД^Чу v |• что пикл°гепеа? вьт^>иведенн^Ю схемУ программы, мы видим, Г т я бы тоиРи н т е о ^ олняется до тех П°Р пока мы не найдем три иk неР ^^еую щ и х нас треугольника (до тех тех поипор, п0‘ ка значение «ьника ^до TnPvmnhHHKH BOOR равно о), д Чт0 будет, если такие П Кэпоолл м о ж е - ? ^ 6 Н6 сУществУ°т? Р аз их не нашел напишем еще д о п о 5 ыть’ и Э ^ М Н6 НайДет? Из осторожности ствии с которым н * следует Проверять т^уТольники СудвоенГ т у Г а Г н Г Г л > * Превышает м а к ^ Г ^ м о е останетсяМтолько^еи/1 ТРИ искомыхпг')еУгольника (^ = 3). т0 ш адь этих тпеугол к * «спечатать. Поскольку мы з н а е м пло­ щ адь этих р у Ь“НиК0В, мы без труда найлем их повторив б ы л о Т ; анН айЛ дяГИкЧНые Т0ЛЬК° что "роиГ

дТ нн1 ш

Хжно

ШТ у с л о в и ю з а д ^ “ й' " ибудь тРе»г«ьннк, удовлетворлютоинее ( п о и м Ж * " ' за" омнить « " « и его сторон, во “ тороны 6 ” оче“ У!>’ зиая "*ВД Д Ь, заново ВЫЧИСпрограмму:' ТаКУИ

“ Р Р « ™ Ровку, напишем законченную

program carroll (o utf)uty L ^ ° б н а я двойная площадь треугольника*) a b k'in te ge r- (* и теты тРеУгольника , ' . & ’ ,чИсло треугольников*) . . . (см. з а д а чК' ,р9е" ь из " aTW “«b»b,x ч и сел .)

ф и п и ^ У -Ш ^ , ральных чисеД звлечении квадРатНого корня из нату. . . (см. з а д 9Чу] 9) begin (* carroll *) s: = 0; 49


repeat s : = s + 1; k := 0 ; a : = l ; b : = s div a; (* первая п ара катетов a ^ b * ) repeat if (a*b = s) and (sqrootmod (sqr (a) -{-sqr ( b )) = 0). then (* гипотенуза — натуральное число *) k : = k -j- 1; a : = a + 1 (* берется н о в а я * ) b : = s div a (* пара катетов *) until a > b until ( k ^ 3 ) or (s = maxint)', (* печатаются найденные треугольники *) if k ^ 3 then begin a : = 1; b : = s div a; repeat if ( a * 6 = s ) and (,sqrootmod (sqr (a)-{-sqr (b)) = 0) then (* гипотенуза — натуральное число *) writeln {a, b, sqroot (sqr (a) + sqr (ft))); a : = a + 1; b: = s div a until a > b end end. Выполнив эту программу, Э В М напечатала такие три треугольника: 15 112 113 24 70 74 40 42 58 © Измените программу так, чтобы она находила и печата­ л а не только три треугольника наименьшей площади, но и все прочие треугольники, площадь которых не превышает s (s — исходное данное) и которые удовлетворяют выше­ указанному условию.

17. Уравнение а 3

Ь3 — с3-{-d3

Вот что рассказы ваю т об индийском м атематике С. Р а ­ мануджане (R a m a n u ja n , 1887— 1920). Как-то раз больного математика навестил его друг-европеец. Гость пожаловался, 50


что прибыл в кэбе с очень скучным номером, и сообщил четырехзначное число. Р ам а н у д ж ан ему возразил: назван ­ ное число очень интересно. Это наименьшее натуральное число, которое можно представить в виде суммы кубов двух натуральных чисел не единственным способом. Составим программу, которая бы нашла это число и на­ печатала бы д ве пары чисел, сумма кубов которых равна найденному числу. Иными словами, требуется найти наимень­ шее натуральное решение уравнения a 3- f b3 = c3 jr d 3, причем а ф с и а ф А . При прочтении условия задачи возникает мысль прове­ рить подряд все числа, пока не будет найдено решение. Одна­ ко такое простейшее решение нерационально. Ведь каж дое проверяемое число надо будет представлять в виде суммы д вух слагаем ы х и проверять, не яв ляю тся ли эти слагаемые кубами каких-нибудь чисел. Поскольку число N можно р а з ­ бить на д в а слагаем ы х N — 1 способом то, чтобы проверить

N первых чисел, необходимо произвести N

Дей­

ствий. Таким образом, число действий пропорционально к ва д р а ту числа, являющ егося решением; поэтому, если реше­ нием является большое число (а какое число будет реше­ нием в действительности, мы не знаем, составляя програм­ му) , д а ж е быстродействующая вычислительная машина будет решать эту з а д а ч у слишком долго. Будем искать лучшее решение, рассмотрев аналогич­ ную, но более простую задачу. Вместо суммы кубов возьмем сумму квадратов, т. е. будем искать наименьшее число, равное двум суммам, к а ж д а я из которых яв л яется суммой квадратов д ву х натуральных чисел, различных в первом и втором случае. Составим таблицу сумм квадратов для первых одиннад­ цати натуральных чисел (табл. 3 ). Левую нижнюю часть таблицы мы не заполняли из-за того, что при перемене мест слагаем ы х мы получили бы ту ж е самую сумму. Чтобы сделать разбор задачи (а позднее и составление программы) более простым, упорядочим с л а­ гаемые в парах таким образом, чтобы первое слагаемое не превосходило второе (а ^ .Ь ). Выберем из таблицы суммы квадратов и ‘запишем их в порядке возрастания: . 2, 5, 8, 10, 13, 17, 18, 20, 25, 26, 29, 3 2 ,3 4 , 3 7 , 4 0 , 4 1 , 4 5 , 5 0 , 5 0 , 5 2 , , . . 51


Наименьшее число, которое повторяется в этой последова­ тельности д в а ж д ы , и будет искомым решением. Таким числом в данной последовательности яв л яется 50. Из таблицы 3 н а­ ходим, что 12 -+- 72= 50 и 52 + 52 = 50. Т аб л и ц а 3

Таблица чисел а 2 + 62

b а

1 2 3

1 2 5 2 8 3 4 5 6 7 8 9 10 11

4

5

10 17 26 13 20 29 18 25 34 32 41 50

6

7

8

9

10

11

37 40 45 52 61 72

50 53 58 65 74 85 98

65 68 73 80 89 100 113 + 128 +

82 85 90 97 106* 117 + 130 145 162

101 104 109 + 116 + 125 136 149 164 181 200

122 + 125 + 130 137 146 157 170 185 202 221 242

Таким образом, д л я того чтобы решить зад ачу, надо уметь найти члены упомянутой последовательности, следую­ щие по порядку др уг за другом. Первые д ва равных члена этой последовательности и будут представлять собой искомое решение. Предположим, что у нас есть процедура, посредством ко­ торой мы по предъявлении любого члена последовательности могли бы получить другой, непосредственно следующий за ним член этой последовательности. Тогда д ля решения з а д а ­ чи мы бы могли составить такую схему программы: program к ва д р а ты (output); v a r стар, Hoe'.integer; procedure новое (s : integer; v a r n ’. integer); находится новый член ряда — п, следующий з а старым членом ряда — s 52


begin нов: = 2 ; repeat с т а р : — нов; новое (стар, нов) until стар = нов; write (стар) end. Эта программа печатает не вполне исчерпывающий ре­ зультат. Следовало бы напечатать не только сумм у к в а д р а ­ тов, но и сами квадр аты , а еще лучше — числа, возводимые в квадрат. Эти числа мы могли бы найти из суммы квадратов, но их можно получить и проще: требуется, чтобы процедура новое производила действия не с суммами, а с возводимыми в квадр ат числами. Внесем изменения в программу и в заголовок процедуры новое ; д ля вычисления суммы квадратов двух чисел введем функцию /: program квад р аты (output)-, v ar а, b, (* первая, стар ая пара *) с, d :in te g e r ; (* вторая, новая п а р а * ) function f (a, b :in te g e r ):in te g e r ; begin /: = а * а + b*b end; procedure новое (a, b'.integer; v a r c, d :in te g e r); (* находим новую пару чисел (с, d ):* ) (* c ^ d , f (a, b ) ^ f ( c , m axint *) (* и нет другой пары (х, у): .*)' {*1 (ft, b ) ^ f (x, y ) < f ( c , d) *) begin (* к вадр аты *) с : = 1; d : = 1; repeat a ' . —'c; b : = d ; новое (a, b, c, d) until f (a, b) = f (с , d); writeln (a, b , c, d, f (a, b)) end. Остается составить процедуру новое, которая будет нахо­ дить новую пару чисел. Проще всего было бы просмотреть все пары чисел и из них выбрать ту, которая удовлетворяет предъявляемым тре­ бованиям. Поскольку таких пар бесконечно много, то попы­ 53


таем ся ограничить область, в которой может находиться пара, удовлетворяю щ ая условию. Заглянем в таблицу 3. Допустим, что ста р ая пара: а = 5, Ь = 9. Сумма их к в а д р а ­ тов /(5, 9 ) = 106 в таблице помечена звездочкой. В каж дой строке таблицы ( а ^ Ь ) можно выделить только по одному кандидату на новую пару — сумму квадратов. Они помечены знаком «плюс». Числа, находящиеся слева от 106, не под­ ходят из-за того, что они меньше 106, а справа — больше, чем обозначенная сумма. В строках, номера которых больше b (в данном случае больше 9 ), т а к ж е не стоит искать нового кандидата, поскольку он будет больше, нежели уж е подобранные. Обнаружив самого подходящего кандидата, т. е. сумм у квадратов двух чисел, получим новую пару чисел. Из этого рассуж дения вытекает, что новая пара (с, d) долж на удовлетворять одному из следующих условий: 1) 1 и d>b; 2) a-\ -\ ^ .a^ L d ^ Z b . Приведем схему процедуры, ищущей новую пару: procedure новое (a, b'.integer; v ar с, d '.integer); v a r fab, (*f (a, b)*) fed, (*f (c, cl)*) x, у '.integer; (* временная пара (с, d)*) begin fed: = m axint; fab : = f (a, b); ищется пара (с, d): 1 ^ c ^ a , d i> b , f a b ^ f ( c , d ) ^ . maxint

ищется пара (с, d): a -\-1 f a b ^ f ( c , d ) ^ . maxint end Запишем на язы ке П аскаль действия, заключенные в прямоугольники: (* ищется пара (с, d): \ ^ с ^ a, d > b *) ( * f a b ^ .f (с, d)<C m a x in t* ) for x : = 1 to a do begin 54


у : — b + 1; while f a b > f ( x , у) do y : = y + 1; if f (x, y ) < f e d then begin fed: = f (x, y); c: = x ; d := y end end; (* ищется пара (с, d ) :a - \ - 1 s ^ c ^ d ^ f t * ) (*fab^Lf (с, d ) ^ maxint *) for x : = a + 1 to b do begin y: =x; w hile (f a b > f ( x , y)) and (y < b ) do У- = y + 1 ; if f (x, у) < fed then begin fed : — f (x, y)\ c: =x\ d := y end end Остается только соединить все части, и мы получим законченную программу, решающую уравнение а 2-\-Ь2 = = c2-{-d2. Чтобы решить зад ачу, сформулированную в н а­ чале, т. е. найти наименьшее число, сл уж ащ ее решением уравнения a 3 + b3= c3 + d3, достаточно только изменить функцию f — вместо суммы квадратов вычислять сумму кубов: f : = а*а*а-\- b*b*b. Обратим внимание на то, что аналогичным образом мы могли бы получить и решения уравнений a 4 + 64 = c4 + d 4, а ъ-1г Ь5 = с5 + d? и т. д. Законченная программа выглядит так : p ro gram кубы ( output ); v ar а, b (* первая пара *) с, d '.integer: (* вторая п а р а * ) function / (a, b :in te g e r ):in te g e r ; (* сумма кубов чисел а и Ь*) begin f : = а * а * а -\ - b*b*b end; 55


procedure новое (a, b :in te g e r ; v a r с, d ’. integer); v a r fab, (*f (a, b)*) fed, (*f (c, d)*) x, у ’. in teg er; (^промежуточные значения с и d*) begin (* новое *) f e d : = maxint; f a b : = f (a, b); (* ищется пара (с, d):\ d > b ,* ) (*fab ^ f (с, d) ^ maxint *) for x : = I to a do begin У ■= 6 + 1; while f a b > f ( x , y) do У- = y + 1; if f (x, y ) < f c d then begin fcd'.f (x, y); c : = x; d := y end end; (* ищется пара (с, d ) : a + 1 - ( * f a b ^ f (c, d ) ^ . maxint *) for x : = a + 1 to b do begin y: =x; while (f a b > f ( x , y)) and ( y < b ) do y : = y + 1; if f (x, y ) c f c d then begin fed : = f (x, y); c: = x ; d:=y end end end; (* новое *) begin (* кубы *) с : = 1; d : = 1; repeat a : = c ; b: = d ; новое (a, b, c, d) until f (a, b) = f (c, d); writeln (a, b, c, d, f (a, b)) end. 56


Выполнив эту программу, ЭВМ н ап ечатала \

1

12

9

10

1729

Значит, число, которым восхитился С. Р ам ануд ж ан, было 1729, так как 1729 = 103 + 93= 123 + 13. © Эту з ад ач у можно решить подобно тому, как решались зад ач и о равновеликих прямоугольниках или треугольниках (см. задачи 15 и 16). Воспользовавшись идеями упомяну­ тых задач, составьте другой вариант программы кубы. Какой из вариантов эффективнее?

18. Задача Антанаса Баранаускаса Автор поэмы «Аникщайский бор» А. Б ара н а уска с (1835— 1902) был не только поэтом, но и математиком-любителем. Особенно интересовался он теорией чисел: он исследовал свойства простых чисел. Приведем одну из самых любимых зад ач его детства, над которой, как он сам позднее писал, бился недели две, пока не нашел решения: сколько можно купить быков, коров и телят, платя за быка 10 р., за корову — 5 р., а за теленка — полтинник, если на 100 рублей надо купить 100 голов скота? Условие этой задачи можно описать при помощи систе­ мы уравнений: / 10 6 + 5 /с+ 0,5 г== 100, { б + /с + г = 100, где б - — число быков; к — число коров; т — число телят. Обе части первого уравнения умножим на 2 (т. е. деньги будем считать в п олтин н иках):

( 20 6 + 1 0 к + г = 2 0 0 , { б + /с + т = 100. Мы имеем систему двух уравнений с тремя неизвестными. В общем случае т а к а я система уравнений имеет бесконеч­ ное число решений. Однако в данном случае число решений конечно. Во-первых, решениями могут быть только неотрица­ тельные числа. Во-вторых, возможные значения .неизвестных ограничены и сверху: за 100 р. нельзя купить более 10 быков (д аж е если покупать только одних б ы к о в ), более 20 коров и более 200 телят. 57


Таким образом, 0<б^10, 0<к<20, 0^г<200. Поэтому зад ач у можно без труда решить, перебрав все возможные значения неизвестных и проверив, удовлетворяют ли они уравнению. Чтобы программа была более экономной, число телят, пользуясь вторым уравнением, выразим через два других неизвестных: т = 100— (б + /с) и будем проверять, перебирая значения переменных б и к, только первое уравнение. Мы составили программу: p ro g ra m скот (output); v a r б, (* быки *} к, (* коровы *) т'.integer; (* телята *) begin for б: = 0 to 10 do for к: = 0 to 20 do begin r : = 100 — (6 + /c); if 2 0 * 6 + 10*/c + т = 200 then begin writeln (’БЫ К О В : , 6 : 4 ) ; writeln (’ КОРОВ: ’, к: 4); writeln ( Т Е Л Я Т : ’, г : 4) end end end. Выполнив эту программу, ЭВМ н ап ечатала такие резуль­ таты: БЫ К О В: 1 КОРОВ: 9 ТЕЛЯТ: 90 Д л я этой зад ач и можно написать более экономную про­ грамму: из внутреннего цикла можно вынести часть дей­ ствий, а кроме того, уменьшить число повторений циклов, поскольку можно доказать, что 0<б<5, 0<к<11. 58


Однако тогда программа станет несколько менее нагляд­ ной, а экономия машинного времени будет невелика, по­ скольку программа рассчитана на однократное выполнение. © Составьте программу д ля нахождения всех н ату р ал ь ­ ных решений уравнения г'3 + / 3 + k 3 = z3 в интервале [1; 20]. Решения, которые получаются, если поменять местами г, /, k, следует отбросить. К аж дое решение уравнения (четыре числа) напечатайте на отдельной строке.

19. Считалка Многие игры, например прятки, салочки, дети начинают со считалок. Д л я того чтобы выяснить, кому придется во­ дить, кто-либо из играющих произносит недлинный стихо­ творный текст — считалку. Играю щ ий, на которого попадает последнее слово текста, выходит из круга. Кто последним останется в кругу, тот и долж ен водить. Предположим, что в кругу стоит п детей, а в считалке т слов. Составим программу, которая напечатает номера детей в том порядке, в каком они выходят из круга. Д етей в кругу опишем как множество v a r к р у г : set of 1..птах где константа пт ах — максимальное число играющих. Перед тем как начинать считалку, имеем следующий круг детей: круг: = [1 ..п] Когда г'-й ребенок выходит из круга,

круг: = к р у г — [г] Считалка закан чи вается только тогда, когда в кругу нико­ го не останется, т. е. круг= [ ] Если номер играющего, только что вышедшего из круга, г, то следующим выходит играющий, номер которого вычисля­ ется следующим образом: for / : = 1 to m do номер играющего, стоящего следом за г 59


Когда дети в кругу от 1 до п - го, то переход от одного игрока к другому можно выразить таким оператором:

i: = г mod п -j- 1 Однако может случиться так, что играющий, номер кото­ рого следует за г ( г + 1 или 1, если i = n ) , уж е в1 шел из круга. В этом случае номер вышедшего из круга участника надо пропустить: repeat

i : = i m od п + 1 until i in круг Приведем законченную программу: p ro g r a m дети (input, output)-, const nm ax = 20; (* максимальное число игроков*) v a r п, (* фактическое число игроков*) пг, (* число слов в считалке *)

i, j'.integer; к р у г : set of 1..птах; begin

read (n, m); круг: = [ 1 ..«]; (* в круге стоит n детей *) i:= n ; repeat for / : = 1 to m do repeat i: = г m od n -f- 1 until i in круг; write (i: 3); круг: = к р у г — [г] until к р у г = [ ] end. © Б. Кордемский приводит [12] такую зад ачу: коту снит­ ся, что его окружили тринадцать мышей. Д ве н а д ц ат ь из них серые, а одна белая. Слышит кот, что кто-то говорит ему: «Мурлыка, ты мож ешь съедать каж д ую тринадцатую мыш­ ку. Считай их по кругу в одном направлении. Белую мышку ты долж ен съесть последней». З а д у м а л ся кот: с какой мышки начинать счет? Составьте программу д ля решения этой задачи. 60


20. Д ел еж Аня н ар в ал а яблок и поровну р а з д а л а своим сестрам Оле, М аш е и Тане, а что осталось, съела. Оля свои яблоки поде­ лила между тремя сестрами, а что осталось, съела. То же самое сделали М аш а и Таня. Сколько яблок о казалось у каждой сестры? Составим программу для решения этой задачи. Каждую из четырех р азд ач можно выразить посредством процедуры, имеющей четыре п арам етра. Обозначим их именами а, Ь, с, d. Это число яблок, которые имеют сестры: до раздачи — при обращении к процедуре и после раздачи — по выполне­ нии процедуры. Процедура очень простая. Составам ее. Будем считать, что первый параметр означает число р а з д а ­ ваемых яблок. procedure дележ (var а, Ь, с, d:integer)\ begin b : = b - \ - a div 3; (* сестрам *) c : = c + a div 3; (* д а л а *) d : = d - \ - a div 3; (* по третьей ч асти *) a: = 0 (* что осталось, съела *) end Одни и те ж е параметры соответствуют и исходным д а н ­ ным, и результатам. Остается только включить процедуру в программу: p ro g r a m яблоки (input, output)-, v a r Аня, Оля, Маша, Таняип(е£ег; procedure дележ (var а, Ь, с, d-.integer)-, begin b : = b - \ - a div 3; (* сестрам *) c : = c + a div 3; (* д а л а *) d : = d - \ - a div 3; (* по третьей части *) a: = 0 (* что осталось, съела *) end; (* дележ *) begin (* яблоки *) read (Аня); (* сколько яблок н ар в ал а Аня *) Оля: = 0 ; Маша: = 0 ; Таня: = 0 ;

дележ (Аня, Оля, Маша, Таня) ; дележ (Оля, Маша, Таня, Аня); дележ (Маша, Таня, Аня, Оля); дележ (Таня, Аня, Оля, Маша); writeln ( П О С Л Е Д Е Л Е Ж А С Е С Т Р Ы И М ЕЮ Т Я Б Л О К );

61


writeln writeln writeln writeln

(|АНЯ: \ Аня)', ( О Л Я : , Оля); (’МАША^ Маша); ( ТАНЯ: ’, Таня)

end Обратим внимание на одну интересную деталь: результат для последнего участника д ел еж а всегда будет равен нулю. 0 Составьте программу д ля решения зад ач и о дележ общем случае, т. е. для д ел еж а яблок между п лицами (п — входное д а н н о е ). Д е л е ж осущ ествляется в соответствии с теми ж е правилами, что и дел еж между четырьмя лицами. Укажите число лиц (максимальное число лиц определите при помощи кон станты ), а количество яблок у каждого из них сохраните в массиве.

21. Наибольший общий делитель Алгоритм получения наибольшего общего делителя, н а­ зываемый алгоритмом Евклида, изложил в своей знамени­ той работе « Н ачал а» Евклид (330— 320 гг. до н. э.). Он остался едва ли не самым популярным алгоритмом и в эпоху электронно-вычислительных машин. Его можно обнаружить во многих учебниках программирования. Уже десять лет, как он и зо бр аж ается на облож ке ж у р н ал а «П рограм миро­ вание». Понадобится он и нам, поскольку функцию наиболь­ шего общего делителя двух чисел мы будем использовать во многих зад ач ах. Приведем два варианта функции (подробнее см. [1 ] ) : Рекурсивный function нод (х, у ’.integer)'.integer; (* наибольший общий д елитель *) (* (х^гО) a n d ( у ^ О ) *) begin if х = 0 then н о д : = у , else н о д : = нод (у mod х, х) end Итеративный function нод (х, у '.integer) : integer; (* (x^sO) a n d u / > 0 ) *) begin while (хфО) a n d ( у ф 6) do if x ^ - y then x : = x m od у else y: = y mod x; 62


но д : = х -f- у end

Оба парам етра функции долж ны представлять собой неотрицательные числа. Когда оба парам етра равны нулю, их наибольший общий делитель обозначаем нулем. 0 Исходные данные — последовательность натуральных чисел. В конце последовательности — нуль. Составьте прог­ рамму для нахождения наибольшего общего делителя всех членов этой последовательности. Наибольш им общим дели­ телем последовательности считается наибольшее н ату рал ь­ ное число, на которое делятся все члены последователь­ ности.

22. Взаимно просты^ числа Числа, у которых наибольший общий делитель равен единице, назы ваю тся взаимно простыми. Составим функцию, устанавливаю щ ую , являю тся ли два числа взаимно простыми: function взаимпрост (х, у '.integer): boolean.-, (* являю тся ли числа взаимно простыми *) function нод (х, у '.integer):integer-, (* наибольший общий делитель *) . . . (см. зад ач у 21) begin (* взаимпрост *) взаимпрост: = н о д (х, у ) = 1 end Эту функцию мы применим в зад ач е о шестеренках. Одна из важнейш их характеристик пары шестеренок — это соотношение между оборотами шестеренок, например 3 :1 , 2 :5 , которое обратно соотношению числа зубцов. Наиболее прочной шестеренкой является та, у которой все зубцы изнаш иваю тся равномерно. А для того чтобы все зубцы стирались равномерно, каждый зубец одной шесте­ ренки долж ен с одинаковой частотой соприкасаться со всеми зубцами другой шестеренки. Нетрудно убедиться, что это условие удовлетворяется, когда числа зубцов двух шесте­ ренок являю тся взаимно простыми. Соотношение всегда можно упростить так, чтобы его члены были взаимно просты­ ми (например, соотношение 9 :1 2 можно заменить на соот­ ношение 3 : 4 ) . М еж ду тем соответствующим образом умень­ шить число зубцов в шестеренках мож но не всегда, посколь63


ку число зубцов не мож ет быть слишком мало (ясно, что, например, не бывает шестеренок, имеющих 1 или 2 зу б ца). Однако можно найти вы ­ ход, дополнив такую п еред а­ чу третьей паразитической шестерней (рис. 7), у которой число зубцов является в з а ­ имно простым с числом зубРис. 7. цов каждой из двух других шестеренок. Составим функцию, значением которой будет наименьшее число зубцов паразитической шестерни z (z s^ 10) при данных двух других числах — количестве зубцов двух (истинных) шестеренок. Если д ан н а я п ара шестеренок удовлетворяет условию равномерного износа, то паразитическая шестерня не нужна и значение функции долж но быть равно нулю: function зубцы (a, b:integer):integer; const с = 10; v a r z: integer; begin if взаимпрост (a, b) then z: = 0 else begin

z:= c; while not взаимпрост (a, z) or not взаимпрост (b, z) do z: = z + 1 end; зу б ц ы : = z end , © Составьте функцию, которая проверяла бы, являю тся ли три числа взаимно простыми. © © Исходные данные — передаточное число пары ш ес­ теренок и минимальное число зубцов в шестеренке. Тре­ буется составить функцию, которая установит, можно ли подобрать шестеренки, удовлетворяющие условию одинако­ вого износа и имеющие число зубцов, не меньшее, чем мини­ мально допустимое. © © © В ведущем зубчатом колесе велосипеда а зуб­ цов, а в ведомом — b зубцов. Составьте программу, которая будет вычислять и печатать последовательный ряд из 20 чи­ сел, соответствующих возможному количеству звеньев в ве~ 64


лосипедной цепи, которое удовлетворяло бы условию равно­ мерного износа. Минимальное число звеньев п = а-\-Ь. © © © © В шестеренке А а зубцов, а в шестеренке В b зубцов. По одному зубцу в той и другой шестеренке специально помечено. В данный момент помеченные шесте­ ренки соприкасаются. Составьте функцию, которая вычисля­ л а бы, через какое минимальное число оборотов помеченные зубцы снова (т. е. во второй раз) коснутся друг друга.

23. Наименьшее общее кратное В одной из египетских пирамид на каменной надгробной плите было обнаружено высеченное иероглифическое обозна­ чение числа 2520. Трудно сказать, почему этому числу была о к а зан а т а к а я честь. Быть может, потому, что оно без остатка делится на все натуральные числа от 1 до 10 (является наименьшим общим кратным всех чисел от 1 до 10). Используя функцию наибольшего общего делителя, легко составить функцию д ля нахождения наименьшего общего кратного двух чисел: 1 function нок (х, у '.integer):integer-, (* наименьшее общее кратное *) (* (л :> 0 ) a n d ( у > 0) *) function нод (х, у '.integer)'.integer; (* наибольший общий делитель *) . . . (см. зад ач у 21) begin (* нок *) нок: = х div нод (х, у) *у end С начала осущ ествляется деление, а затем — умножение, чтобы уменьшить вероятность переполнения. Применяя приведенную функцию нок, составим програм­ му для печати таблицы наибольших общих кратных всех идущих по порядку натуральных чисел от 1 до л (п — входное д а н н о е ): p ro g ra m кратное (input, output); v a r k, кратн, n: integer; function нок (x, у '.integer): integer; (* наименьшее общее кратное *) function нод (x, у '.integer)'.integer; (* наименьший общий делитель *) begin 3 В. А. Дагене

65


if x = 0 then н о д : = у else нод: = н о д (у mod х, х) end; (* нод *) begin нок: = х div нод (х, у) * у end; (& нок *) begin (* кратное *)

read (п); кратн: = 1; for k: = \ to я do begin

кратн: =HOK(k, кратн); writeln (k, кратн) end end. © Исходные данные — последовательность натуральных чисел. В конце последовательности — нуль. Составьте программу для нахождения наименьшего общего кратного всех членов ряда. Кратным р яд а считается наименьшее натуральное число, которое делится без остатка на все члены ряда. © © Составьте функцию, значением которой было бы наименьшее общее кратное всех натуральных чисел от 1 до п включительно (п — параметр функции). © © © Составьте программу д ля нахождения макси­ мального вмещ аю щ егося в ЭВМ числа, которое без остатка делится на как можно большее количество следующих друг за другом натуральных чисел.

24. Бильярд Запечено, что математики лю бят играть на бильярде. Возможно, это потому, что траекторию ш ара, отскакиваю щ е­ го от краев бильярдного стола, можно вычислить д о ста­ точно точно. Приведем зад ачу, тесно связанную с б ильяр­ дом. Предположим, что дан прямоугольник, длины сторон ко­ торого в ы раж аю тся натуральными числами а и Ь. Из одной вершины прямоугольника проводится линия, которая состав­ ляет угол в 45° со сторонами прямоугольника. Д ойдя до стороны прямоугольника, линия, не пересекая ее, и зл ам ы ­ вается под углом в 90°. Д о й д я до другой стороны, она снова изламы вается под углом в 90° и т. д. до тех пор, пока она не дойдет до какой-либо из вершин прямоуголь­ ника. (Убедитесь, что это произойдет во всех случаях, когда 66


b a-(b-a-a)

Рис. 8. Схема установления числа отрезков траекторий би льярдного мяча, вы читая короткую сторону.

длины сторон прямоугольника выражены натуральными числами.) П олучаю щ аяся л о м ан ая напоминает траекторию б и л ь ­ ярдного ш ар а на прямоугольном столе. Составим функцию бильярд , которая будет устанавли ­ вать, из скольких отрезков состоит л о м ан ая (концы отрезков должны находиться на сторонах прямоугольника). Можно составить эту функцию несколькими способами. Каким об­ разом производятся вычисления, показано на рисунке 8. (Чтобы не заг р ом ож дать чертеж, мы не провели ломаную до конца.) Длинную сторону заданного прямоугольника обозначим max, короткую — min. Тогда функция бильярд будет выгля­ деть следующим образом: function бильярд (a, b '.integer):integer-, v a r max, (* длинная сторона прямоугольника *) min, (* короткая сторона прямоугольника *) п, (* число отрезков в ломаной *)

d : integer-, begin if a ^ b then begin m a x : = a \ m i n : = b end else begin m a x : = b ; m i n : = a end; n :=0;

d: = m in : whiie d -Ф 0 do begin

d: = m a x — d; n: = = « + 1; whiie d ^ m i n do 67


begin

d: = d — min; n: = n + 1 end; if

then begin

d \ = m i n — d; n := n+1 end end;

б ильярд: = n end

Рис. 9. Пример траектории б и л ь я р д ­ ного мяча.

Значительно лучший вариант функции можно составить, если использовать наименьшее общее кратное длин сторон — нок (см. зад ач у 23). Разберем пример, приведенный на рисунке 9. Развернем этот чертеж таким образом, чтобы л ом ан ая шла только

Тогда лом аная, вместо того чтобы о т р аж ат ь ся вт корот­ кой стороны, пересекает ее. Получаю тся отрезки одинаковой длины. Их число равно (убедитесь!): нок (min, max) div min Чтобы получить упоминаемое в зад ач е число отрезков, остается прибавить внутренние короткие отрезки, которых всего имеется нок (min, max) div m a x — 1 Значит, траектория бильярдного ш ара состоит из

нок (min, max) div min + нок (min, max) div m a x — 1 отрезков. Поскольку, поменяв местами длины сторон min и max, мы просто поменяем местами первые два слагаемых (при перемене мест слагаемых сумма не меняется), то приведен68


ную выше формулу можно упростить, если не обращ ать внимания на то, к а к ая из сторон длиннее:

нок (a, b) div а-{-нок (а, b) div b — 1 Таким образом, вся функция выглядит так: function бильярд (a, b :integer):integer; function нок (х , y:integer):integer; (* наименьшее общее кратное *) . . . (см. зад ач у 23) begin (* бильярд *) бильярд: = нок (a, b) div а-\-нок (a, b) div b — 1 end

25. Делители Самый простой способ найти все делители числа п — это проверить по очереди делимость п на каж дое из чисел 1, 2, 3, ..., п. Легко убедиться, что в интервале [п div 2 + 1 ; п — 1] делителей числа п нет. Д л я нахождения делителей числа п составим такую про­ цедуру: procedure делители (п:integer); v a r d'.integer; begin for d: = 1 to n div 2 do if n mod d = 0 then

write (d); write (n) end Например, когда я = 30. Выполнив эту процедуру, ЭВМ напечатает следующие результаты — делители числа п: Г

2

3

5

6

10

15

30

Обратим внимание на то, что, д ля того чтобы найти делитель числа п, достаточно обнаруж ить делители, не пре­ вышающие л[п. Все остальные делители получаются в р е­ зультате деления числа п на найденные делители. Например, если и = 30, то достаточно найти делители 1, 2, 3, 5 (натуральный квадратны й корень из 30 равен имен­ но 5), а все прочие делители получаем следующим о б ­ разом: 69


30 30 30 30

div div div div

1=30; 2 = 15; 3=10; 5 = 6.

Составим процедуру, которая будет находить только что рассмотренным способом делители любого данного числа. При составлении можем использовать либо цикл for (в этом случае придется д ля извлечения квадратного корня из нату­ ральных чисел использовать функцию sqroot — см. зад ач у 9), либо цикл while. Выберем последнюю возможность: procedure делители ( п '.integer); v a r d: integer; begin d: = 1; while d*d<Ln do begin if n mod d = 0 then write (d, n div d);

d := d + \ end; if d*d = n then write (d) end Чтобы не печатать два одинаковых делителя, когда число является точным квадратом (например, 4 и 4, когда п — 16), : следует ввести дополнительный условный оператор: if d*d = n then write (d) Эта процедура эффективнее, нежели приведенная выше, поскольку цикл выполняется меньшее число раз. Однако она печатает результаты в другом порядке, нежели преды- , дущ ая. Например, когда « = 30, результаты процедуры будут напечатаны следующим образом: 1 .3 0

2

15

3

10

5

6

Чтобы делители были напечатаны в порядке возрастания, надо было бы печатать только делители от 1 до -yjn и запоминать их — сохранять в массиве или в файле. Все прочие делители можно получить (или сразу напечатать), разделив число п на каж ды й из имеющихся в памяти делителей, начиная с конца (прежде всего надо делить на последний из имеющихся в памяти делителей и т. д .). Однако этот метод обладает недостатком: мы не можем заранее определить размер массива (предельный), поскольку 70


мы не знаем, сколько будет делителей. А просмотреть файл в обратном порядке достаточно сложно. Ещ е один способ запомнить значения и позже использо­ вать их в обратном порядке — это рекурсия. Составим рекурсивную процедуру: .av ■:■■ - 1■ - > ■г;\V •'■ г 1 ■- ■ЦБ : procedure дел (п, d: integer)-, v a r dd: integer-, begin while (n m od d=/= 0) a n d (d*d<Cn) do (* пропускаются *) d : = d - \ - 1; (* все числа, не являю щ иеся делителями п *) if d * d ^ n then (* найден делитель *) begin write (d); (* печатается делитель *) dd: = п div d; (* и запоминается его двойник*) if d=/=dd then begin дел (n , 1);

write (dd) end end end Эта процедура печатает первый найденный делитель d и запоминает его двойника — число п div d. Если двойник не равен делителю, то снова происходит рекурсивное о б р а­ щение к той ж е самой процедуре. Чтобы при очередном обращении к процедуре обнаружить делитель, надо при каждом новом обращении указывать, с какого места начи­ нать поиск делителей. Д л я этой цели служ ит второй параметр процедуры. Нецелесообразно требовать, чтобы пользователь проце­ дуры при обращении к ней долж ен был каж ды й раз писать одно и то ж е значение второго параметра. Поэтому надо «спрятать» от пользователя второй параметр. Сделаем это, заключив приведенную процедуру в другую процедуру, в которой есть только один параметр: procedure делители ( п -.integer); procedure дел (п, d: integer); v a r dd:integer; begin while (n m od d=£ 0) a n d (d*d<Cn) do (* пропускаются *) d: = d + l ; (* все числа, не являющиеся делителями п ■*) if d * d ^ n then (* найден делитель*) 71


begin

write, (d); (* печатается делитель *) d d : = n div d ; (* и запоминается его двойник*) if d ^ d d then begin

дел (n , d + 1); write (dd) end end end; begin

дел (n , 1) end Теперь, когда процедура дел спрятана от пользователя и он не видит ее параметров, можно сократить эту про­ цедуру, добавив ещ е один параметр: procedure делители (п: integer)-, procedure дел (п, d, dd'.integer)-, begin if d ^ d d then write (d)\ (* печатается наименьший делитель *) repeat d : = d - \ - 1; (* ищется новая п ара дел ител я*) until (d * d > n ) or (n mod d = 0); if d * d ^ . n then дел (n, d, n, div d)\ write (dd) (* печатается двойник *) end; begin (* делители *) дел (n, 1, n) end © Составьте функцию, находящую, сколько делителей имеет данное натуральное число. Например, когда дано число 10, значение этой функции долж но быть 4, поскольку у числа 10 четыре делителя: 1, 2, 5, 10. © © Составьте функцию, которая будет находить сумму делителей данного натурального числа.

26. Простые числа Простые числа — это натуральные числа, большие едини­ цы, которые имеют только два делителя: единицу и само это число. Хотя многие задачи, связанны е с простыми числа­ ми, формулируются достаточно просто, решить их бывает очень трудно. Некоторые свойства простых чисел еще не 72


открыты. Это побудило Г. Вейля (W ayl, 1885— 1955) так охарактеризовать простые числа: «Простые числа — это т а ­ кие существа, которые всегда склонны прятаться от иссле­ дователя». Во все времена люди хотели найти как можно большее простое число. Пока люди считали только при помощи к а р ан д а ш а и бумаги, им нечасто удавалось обнаружить новые простые числа. Д о 1952 г. самое большое известное простое число состояло из 39 цифр. Теперь поиском все боль­ ших простых чисел занимаю тся ЭВМ. Это может представить интерес д ля любителей рекордов. Мы не будем гнаться за рекордами, а только продемонст­ рируем, как составить программу, которая будет проверять, является ли число простым. Самый простой путь решения этой задачи — проверить, имеет ли данное число п ( п ^ 2) делители в интервале [2; п — 1]. Учитывая, что все четные числа, делящ иеся на 2, являю тся составными и что в ин­ тервале \п div 2 - f - 1; п — 1] не могут находиться делители чис­ л а п, составим такую функцию: function простое (,п : integer)'.boolean; (* является ли число простым *) v a r j'.integer; begin if n = 2 then простое: = true else if not odd (n ) then простое: = false else begin j: = n div 2; if not odd (j) then / : = / - ) - 1; while n m od j Ф 0 do / : = / — 2; простые: = j — 1 end end Чтобы найти все простые числа в заданном интервале, следовало бы проверить к а ж д ое число, находящ ееся в данном интервале,— простое оно или нет. Однако для этого машине пришлось бы потратить очень много времени. Более со­ вершенный способ обнаруж ения простых чисел мы опишем в зад ач е 29. © Д ан о простое число. Составьте функцию, которая б у­ дет находить следующее з а ним простое число (т. е. бли­ ж айш ее простое число, большее данного,— параметр функ­ ции). Например, если дано число 11, то значение функции долж но быть 13, если дано 23, то значение 29. Если входное 73


данное не является простым числом, т. е. не выполняется условие, то значение функции считайте равным нулю.

27. Числа Мерсенна Видный французский физик и популяризатор науки М. Мерсенн (M ersenne, 1588— 1648) заметил, что многие простые числа имеют вид 2Р— 1; здесь р так ж е является простым числом. Все числа упомянутого вида называются числами Мерсенна. Однако не все числа Мерсенна являются простыми. В течение почти 200 лет математики считали, что число Мерсенна 267 — 1 является простым. И только в 1903 г. было доказано, что оно представляет собой произве­ дение двух простых чисел: 193 707 721 и 761 838 257 287. Д о сих пор неясно, бесконечно много простых чисел Мерсенна или ж е их число конечно. Составим программу для нахождения всех чисел М ерсен­ на, «умещающихся» в памяти ЭВМ. Приведем схему программы: p ro g r a m мерсенн (output); v a r п о л у т а х М , р; м ерс:integer\ (* кандидат в числа Мерсенна *)

в с е '.boolean-, function простые (х:integer): boolean-, (* является ли число простым *) . . . (см. зад ач у 26) begin (* мерсенн *) полуmaxint : = maxint div 2; р: = 2 ; мерс: = 3 ; (* первый кандидат действительно *) (* является числом Мерсенна *) все: = false-, repeat if простые (р) then write (мерс)-,

p := p + l; ищется новое число Мерсенна; если найденное число превышает maxint, переменная все принимает значение true until все end. 74


Было бы неразумно при каждом новом значении р заново вычислять значение 2Р— 1. (Сколько потратим времени на возведение в степень!) Д о к аж ем , что каж д ое следующее число Мерсенна мьГможем получить таким образом: удвоить имеющееся число Мерсенна и прибавить 1. В самом деле, 2 Р + 1 — 1 = 2Р - 2 — 1 = 2 ( 2 р - 1 ) + 1 . имеющееся число Мерсенна

Новый кандидат в числа Мерсенна подбирается д о ста­ точно осторожно, чтобы не было превышено максимально допустимое число maxint. Итак, запишем операторы для нахождения нового канди­ д а т а в числа Мерсенна: if мерс ^ полу m axint then мерс: = мерс* 2 else в с е : = true if M epc< .m axint th en мерс: = мерс else все: = tr u e Законченная программа выглядит следующим образом: p ro g r a m мерсенн (output); v a r полутахШ , р; Mepc:integer; (* кандидат в числа Мерсенна *)

в с е '.boolean; function простое (п: integer): boolean; (* является ли число простым *) . . . (см. зад ач у 26) begin (* мерсенн *) полут ахШ : = maxint div 2; р : = 2; мерс: = 3 (* первый кандидат действительно*) (* является числом Мерсенна *)

все: — false; repeat if простое (р) then write (мерс); р : = р + 1; (* новый кандидат в числа Мерсенна *) (* подбирается осторожно, чтобы не было превышения *) (* максимального допустимого числа maxint *) if м е р с ^ п о л у т а х Ш then мерс: = м ер с *2 else все: = tru e ; if M ep c C m a x in t then мерс: = м ерс-\-\ else все: = t r u e until все end. 75


Приведем числа М ерсенна, найденные ЭВМ: 3 , 7 , 3 1 , 1 2 7 , 2047, 8191, 131071, 524287, 8388607, 536 870 911, 2 147 483 647. В приведенной программе переменная р принимает зн а ч е ­ ния всех натуральных чисел подряд (2, 3, 4, 5, ...). Однако, чтобы искомое число было числом Мерсенна, р долж но быть простым. Поэтому сразу можно отбросить хотя бы четные числа (за исключением 2). Учитывая это, усовершенствуйте программу.

28. Разложение на простые множители К аж дое составное число можно единственным способом представить в виде произведения простых чисел. Например: 20 = 2 - 2 - 5 ; 21 = 3 -7 . Такое разлож ение на множители осуществляется следую­ щим образом. Заданн ое число делится поочередно на следую­ щие друг за другом по порядку простые числа. Если на какое-либо из них оно делится без остатка, то данное простое число является множителем. Чтобы найти другие простые множители, надо полученное частное разделить на это же число или на следующее по порядку простое число и т. д. Поскольку найти простые числа нелегко, то проще сн ачала делить на 2, а затем — подряд на все нечетные числа. Простое число нельзя разл о ж и ть на простые множители. Приведем составленную нами программу, которая п ечата­ ет все простые множители любого данного числа (если число простое, то печатается оно само). p ro g r a m множ (input, output)-, v a r i, n: integer-, begin

read (n)-, while n mod 2 = 0 do begin (* n делится на д ва *) write (2); n: = n div 2 end;

i: = 3 ; while do if n m od i = 0 then (* n делится на i *) 76


begin

write(i); n: = n div i end else = i + 2 (* переход к следующему *) (* нечетному делителю *) end. © Измените программу разл ож ени я числа на простые множители таким образом, чтобы результат печатался в такой форме:

u = i\*ii*h*. ■Мп, где и — входное данное; i 1, . . ., in — простые множители. © 0 Составьте функцию для нахождения наименьшего нечетного неравного единице натурального делителя любого заданного числа. © © © Составьте функцию, значение которой будет равно числу различных простых множителей заданного числа. © © © © Составьте программу д л я проверки, можно ли заданное натуральное число представить в виде: а) произведения двух простых чисел; б) произведения трех простых чисел; в) кв ад р ата какого-либо простого числа; г) куба какого-либо простого числа. Следует н апечатать ответ Д А или НЕТ.

29. Эратосфеново решето .

Греческий математик Эратосфен (275— 194 гг. до н. э.) предложил интересный метод нахождения простых чисел в интервале [2; п]. Он написал на папирусе, натянутом на рамку, все числа от 1 до 1000 и прокалывал составные числа. Папирус стал как решето, которое «просеивает» составные числа, а простые оставляет. Поэтому такой метод назы вается Эратосфеновым решетом. Поясним этот метод. Пусть написаны все числа от 2 до п: 2

3

4

5

6

7

8

9

10

11

12 . . .

Первое неперечеркнутое число в строке является простым. Таким образом, 2— простое число. Начинаем «просеива­ ние» с него, перечеркивая все числа, которые делятся на 2, т. е. каж д ое второе число: 2

3

$

5

0

7

.$

9

1/0

11

1/2

... 77


Д а л е е берем следующее по порядку неперечеркнутое число и перечеркиваем все числа,, кратные ему и т. д. Таким образом, мы перечеркнем все составные числа, а простые останутся неперечеркнутыми: 2

3

i

5

0

7

£

9

1/)

11

1/2

. . .

Естественно будет все числа указанного интервала р а с ­ сматривать как множество и в дальнейшем из этого множе­ ства исключать (отсеивать) все составные числа. Определим тип такого множества: const п = . . . \ (* верхняя граница и н тервал а*) type числ = set of 2 ..п; (* решето *) Составим процедуру, которая будет формировать мно­ жество простых чисел: procedure г (var s '.huca); v a r i, j:integer-, begin s : = [ 2 . . n ] ; (* в множество включаются все*) (* числа в интервале [2; п\ *) for г: = 2 to п div 2 do if i in s then (* i простое *) begin /': = / + /; while j ^ n do begin s: = s — [/]; / : = / + *' end end end Обратим внимание на то, что наименьшее число, на которое делится некоторое еще неперечеркнутое число п, не превышает целой части квадратного корня из числа п. Это нетрудно доказать. Попытайтесь сделать это! Поэтому цикл for i : = 2 to п div 2 do лучше заменить на цикл for г: = 2 to sqroot (п) do (опираясь на описанную в зад ач е 9 функцию извлечения квадратного корня из натуральных чисел). В языке П а ска ль не определено максимально допустимое число элементов множества — все зависит от того, на чем реализуется язык. Ч а щ е всего в целях экономии памяти для всего множества выделяется только одно слово ЭВМ. Тогда число элементов в множестве ограничено числом бит в слове, а их бывает немного (около 30— 80). Поэтому, 78


для того чтобы расширить интервал искомых простых чисел, можно заменить тип множества на тип логического массива или разбить одно большое (математическое) множество на несколько маленьких, т. е. представить большое множество в виде массива малых множеств. Разберем оба случая. Логический массив. Определим тип массива: const « — (* верхняя граница интервала *) type числа = a r r a y [2..п\ of boolean; (* реш ето*) Д л я каж дого числа данного интервала выделяется один элемент массива. Процедура решето д о л ж н а присвоить з н а ­ чение true тем элементам массива, индексы которых пред­ ставляю т собой простые числа, и значение false — элемен­ там, индексы которых — составные числа. Составим такую процедуру: procedure решето (var s: числа); (* нахождение простых чисел *) (* методом Эратосфенова реш ета *) v a r i, j'.integer; (* глобальная константа п *) function sqroot (х :integer)'.integer; (* квадратный корень из натуральных чисел *) . . . (см. зад ач у 9) begin (* решето *) for i : = 2 to п do .s ft]: = true; for t: = 2 to sqroot (n) do if s ft] then begin j : = t + t; while / do begin

s\j}: = false; /:= /+ * end end end Это простая процедура. Однако для массива s требуется много места в памяти. М ассив м нож еств. Определим тип массива множеств: const и пгах = . . .

v тах = . . . п =. .. type ss = a r r a y \0..umax\ of set of 0 ..vmax 79


2

3

4

5

6

7

8

9

JO 11 12 13 1

0 !

1

2

J

SS[o]

0

1

2

3

0

SS [/]

1 \!

\

2

3

1

SS [2]

Рис. 11: Множество s: set of 2:.13 изображено массивом множеств ss: array [0..2] set of 0..3.

К аж ды й элемент большого м ножества set of 2 ..n должен иметь аналог в массиве ss малых множеств. Пример соответствия (при п = 13 ит ах = 2, vm ax = 3) показан на рисунке 11. Нетрудно найти и общее вы ражение зависимости между элементами обоих типов. Элемент i большого множества и зображ ается посредством элемента (г — 2) m od (v m a x -\- 1) малого множества, который находится в элементе массива (г — 2) div (vmax + 1 ) . Чтобы в массиве нашлось место для всякого элемента большого множества, долж но выполняться условие (iи т а х - \- 1) * (vmax-\- 1 ) ^ я — 1. Составим процедуру, результатом которой будет массив множеств: procedure рш (var s:ss); (* нахождение простых чисел методом Эратосфенова *) (* решета посредством использования множеств *) v a r i, j : integer ; (* глобальные константы п, итах, vm ax *) begin for г: = 0 to ит ах do s [г]: = [ 0 ..vmax]; for i: = 2 to sqroot (n) do if (i — 2) m od (v m a x - \- 1) in s[(i — 2) div (v m a x -\- 1)] then begin

j: =i-i~i; while j ^ n do begin s [(/ — 2) div (v m a x -\- 1)]: = s[(j — 2) div ( vm a x - 1-1)] — [(/ — 2) m od (vmax-\~ 1)]; /:= /+ « ' 80


f-Г"

end cud

end Это более сл о ж н ая процедура, зато для нее требуется меньше места в памяти, поскольку почти во всех ЭВМ з н а ­ чения, относящиеся к типу множеств, кодируются экономно. Включим эту процедуру в программу, печатающую все простые числа, находящ иеся в интервале [2; 10 ООО]: p ro g r a m эратосфен (output); const п = 10000; u m a x = 199; vm ax = 49; и (* ( u m a x - \ - 1)*(vmax-\- \ ) ^ n — 1*) type ss = a r r a y [0,.umax\ of set of 0 ...vmax; var r:s s ;

i:integer; function sqroot (x:integer):integer; (* квадратный корень из натуральных чисел *) . . . (см. зад ач у 9) p rocedu re риг (var s :s s ); (* процедура данной задачи *) begin (* эратосфен *)

рш(г); for i : = 2 to п do if (г — 2) m od (v m a x -j- 1) in r[(i — 2) div (vmax + 1 ) ] then (* простое число *)

write(i) end. Когда при решении каких-либо других з а д а ч нам понадо­ бится найти простые числа в каком-нибудь определенном интервале, то мы прибегнем к помощи приведенной здесь процедуры решето (в которой используется логический м а с ­ сив), поскольку она проще. Если же требуется экономить память ЭВМ, эту процедуру нетрудно заменить на про­ цедуру рш. © Известно новое решето д л я нахождения простых чи­ сел; 4 7 10 13 16 19 . . . 7 12 17 22 27 32 . . . 10 17 24 31 38 45 ... 13 22 31 40 49 58 ... 81


Это таблица, состоящ ая из бесконечного множества бес­ конечных арифметических прогрессий; заметим, кроме того, что каж ды й член первой прогрессии 4, 7, 10, 13, 16, 19, . . . начинает новую прогрессию. Разностью прогрессий являю тся поочередно все нечетные числа начиная с 3:

d i = 3 , й?2 = 5, о(з = 7, d\ = 9

-

и т. д.

Если в этой таблице есть какое-либо число N, то 2 ^ + 1 всегда является составным числом. Если ж е числа N нет, то 2 N - \ - l — простое число. Таким образом, подставляя в формулу 2Л7 + 1 на место N подряд все числа, которых нет в таблице («просеянные» ч исла), мы можем получить все простые числа, за исключением числа 2. Например, в таблице нет числа N — 5, значит, 2jV + 1 = = 11 — простое число; в таблице нет числа N — 6, значит, 2N 1 = 13 — простое число и т. д. Сообразуясь с этим решетом составьте программу для нахождения и напечатания всех простых чисел от 2 до 20 ООО. Примените д л я этого решета массив множеств a r r a y [0..199] of set of 0..49

30. Близнецы Д в а нечетных простых числа, р азн ящ их ся на два, н азы ­ ваются близнецами. Близнецами являю тся; например, числа 5 и 7, 11 и 13, 17 и 19 и т. д. В начале натурального ряда такие пары чисел встречаются достаточно часто, но, по мере того как мы продвигаемся в область больших чисел, их становится все меньше и меньше. Известно, что в первой сотне имеется целых 8 близнецов, дал ьш е они расположены очень неравномерно, но мы обнаруж иваем их все реже, гораздо реже, нежели сами простые числа. До сих пор неясно, конечно ли число близнецов. Более того, еще не найден способ, посредством которого можно было бы разрешить эту проблему. Составьте программу, которая будет находить все числа близнецы в интервале [2; 1000]: p ro g r a m близнецы (output); const п = 1000; type числа = a r r a y [2...я] of boolean; (* реш ето*) v a r р :числа;

i: integer; 82


procedure pt'iiirn) ivm

.

чис щ);

(* нахождение простых чи сел *)

(♦методом Э р п то сф етж п решета к) . . . (см. зад ач у 29) begin (* близнецы *) решето ( р ) ;

i:

— 5;

repeat if p[i\ a n d p[i — 2] then (* i и i — 2 — близнецы *) writeln (i — 2, i); i: = i+ 2 until i > « end. Выполнив эту программу, ЭВМ наш ла в первой тысяче целых 35 пар близнецов. Чтобы находить близнецов в боль­ шем интервале, следовало бы увеличить константу п. Однако, увеличивая ее, можно быстро превысить пределы в о зм о ж ­ ностей ЭВМ: массив числа не уместится в памяти. З н ач и ­ тельно больший числовой интервал можно обследовать при помощи процедуры рил, использующей множества (см. з а д а - чу 29). © Составьте функцию, которая будет проверять, яв л яю т­ ся ли числа находящиеся по обе стороны от заданного четного числа, близнецами.

31. Скатерть Улама О днаж ды математик Станислав Улам ( Ulam, род. 1909) слушал какое-то очень длинное и скучное сообщение. Ж е л а я как-то отвлечься, он разделил лист бумаги на клетки и уже собирался зан яться составлением шахматных этюдов, но передумал и, написав в центре 1, начал писать по спирали против часовой стрелки все натуральные числа подряд, обводя простые числа в круж ок (рис. 12). Скоро, к его удивлению, простые числа выстроились в довольно-таки закономерном порядке, об разуя интересный узор. Этот узор позже стал объектом исследования и получил название скатерть Улама. Составим программу, позволяющую нарисовать скатерть Улама размером 100Х 100 клеток. Не будем печатать числа, а только поставим звездочку (*) на месте простых чисел, Чтобы решить эту задачу, прежде всего надо найти все простые числа в интервале [1; 10 000]. Д л я этой цели 83


используем процедуру решето, составленную по методу Эратосфенова решета (см. зад ач у 29). Кроме того, надо о б р а ­ зовать спираль, состоящую из элементов одномерного м а с­ сива. Подчеркнем, что д ля этой задачи можно составить р а з ­ личные программы. Рассмотрим некоторые способы. 1. Геометрический

Клетки скатерти заполняю тся в том ж е порядке (т. е. элементы решета развор ач и ваю тся начиная от центра с к а ­ терти), как рисовал их Улам. В этом случае мы моделируем в программе геометрические действия. 2. Вычисление координат скатерти

Составляется процедура, в которой входное данное — индекс элемента решета, а результат — координаты соответ­ ствующей этому индексу клетки скатерти. Если мы будем просматривать элементы решета по порядку, начиная с самого малого индекса, то будем заполнять клетки скатерти в том же самом порядке, что и при первом способе. 3. Вычисление индекса (координаты ) элем ента реш ета

С оставляется функция (или процедура), в которой исход­ ные данные — координаты клетки скатерти, а результат — индекс элемента решета, соответствующего данной клетке. При наличии такой функции можно заполнять клетки ск а ­ терти, просматривая эти клетки в каком угодно порядке. П росматривая клетки слева направо и сверху вниз, т. е. в том порядке, в каком символы печатаются, можно вовсе не запоминать клетки скатерти, а просто печатать их. Таким

© 96 95 94 93 92 91 65 64 63 62 © 60 © 58 57 90 66 © 36 35 34 33 32 ® 56 © © 38 © 16 15 14 © 30 55 88 66 39 18 © 4 © 12 © 54 87 69 40 ® 6 1 ©© 28 © 86 70 © 20 0 8 9 10 27 52 85 ® 42 21 22 © 24 25 26 51 84 72 © 44 45 46 © 48 49 50 ® © 14 15 16 77 18 ® 80 81 82 100 99 98

84

Рис. 12. Спираль Улама.


У

wo

— -- ■*--

51 50

1

0

1

. . .

50 51

100

X

Рис. 13. Схема вращения спирали Улама.

образом, если используется этот способ, в памяти нужно меньше места, нежели при способах, описанных выше. Составим программу для геометрического способа о б р а ­ зования скатерти. Поскольку у нас есть процедура решето, которая нахо­ дит все простые числа в интервале [2; п\ и сохраняет результаты в массиве a r r a y [2..п] of boolean (напомним, что значение true присваивается, когда индекс элемента явлется простым числом, a false — в противном случае), то наиболее сл ож н ая часть решения — упомянутый одно­ мерный массив (точнее, его индексы) скрутить в виде спира­ ли. Как мы будем скручивать его, изображ ено на рисунке 13. Рассмотрим его. Скатерть разделена на квадраты, вписанные один в другой. В каж дом квадрате все четыре линии (стрел­ ки) имеют одинаковую длину. Д лина стрелки зависит от квадрата, которому она принадлежит. Эти длины равны соответственно 1, 3, 5, 7, ..., 99 клеткам. Составим схему программы: p ro g r a m скатерть (output)-, 85


const m = 100; (* скатерть m*m клеток*) (* m — четное число *) « = 10000; (* m*m = n * ) type числа = a r r a y [2. .n] of boolean ; (* реш ето*) v a r скат: a r r a y [1 ..m, 1,.m\ of boolean ; (* скатерть *) p : числа; x , у, (* координаты клеток скатерти *) d:integer; (* на сколько клеток идти*) (* в одном направлении *) procedure решето (var s: числа); (* нахождение простых чисел *) (* методом Эратосфенова решета *) ... (см. зад ач у 29) begin (* скатерть *)

решето(р); х : = п г div 2; у: = m div 2; (* начало в р ащ е н и я* ) d: = 1; (* длина линии (стрелки) *) repeat заполняется л ев ая линия (вертикальная стрелка)

заполняется ниж няя линия (горизонтальная стрелка)

заполняется п р авая линия (вертикальная стрелка)

заполняется верхняя линия (горизонтальная стрелка)

переход в больший квад р ат

d: = d + 2 until d = m-\- 1; печать скатерти end. 86


Первые четыре (центральные) клетки удобнее заполнить отдельно. Д а л ь ш е клетки заполняются в соответствии со специальным законом. Р азо б рав схему вращения, приведен­ ную на рисунке 13, мы можем описать заполнение клеток скатерти следующим образом:

вниз вправо вверх влево Следовательно, цикл repeat в первых четырех прямоуголь­ никах можно было бы детализировать так: for г : = 1 to d do ш аг вниз for z : = 1 to d do шаг вправо for

2

: = 1 to d do

шаг вверх for z : = 1 to d do ш аг влево В прямоугольнике ш аг вниз нам нужно записать т ак ж е действия: У- = у — 1;

k : = k - \ - \ ; (* берется следующий элемент решета *) скат \х, у \ : = р [k] В прямоугольнике ш аг вправо выполняются такие действия: х: = х +

1; 87


f t : = f t + l ; (* берется следующий элемент решета *)

скат [х, у}: — p[k] Аналогичные действия мы долж ны были бы выполнить и с другими прямоугольниками: ш аг вверх и ш аг влево. Поскольку действия во всех четырех прямоугольниках очень похожи, мы можем все их соединить в один общий цикл. Самое трудное место в процессе заполнения клеток — это переход из одного (малого) кв ад р ата в другой (боль­ ший). Это можно запрограм мировать различным образом. В приведенном здесь варианте программы движение начи­ нается из левой верхней (угловой) клетки квадрата: с н а ­ чала мы передвигаемся на одну клетку (делается один ш а г ) , а затем заполняем клетку. Переход из одного квад рата в другой записы вается при помощи следующих операторов присваивания:

х: = * — 1; У - = У + 1. Подчеркнем еще, что перейти в правый кв ад р ат удоб­ нее всего в цикле rep eat — еще до всякого вращения. Поэто­ му наш а программа несколько отличается от приведенной выше схемы. Приведем составленную программу: p ro g r a m скатерть (output)-, const m = i 0 0 ; (* скатерть m*m клеток *) (* m — четное число *) п = 10000; (* m*m = п *) type числа = a r r a y [2..re] of boolean; (* решето *) v a r скат: a r r a y \\..m, L.m] of boolean; (* скатерть *).

идти: (вниз, вправо, вверх, влево); р: числа; х, у, (* координаты клеток скатерти *) d, (* на сколько клеток идти *) ft,

(* в одном направлении *) (*индекс элемента, взятого из реш ета *)

z'.integer; procedure решето (var s : числа); (* нахождение простых чисел *) (* методом Эратосфенова решета *) ... (см. за д а ч у 29) begin (* скатерть *)

решето (р); x : = m div 2; у : = т div 2; (* н ачало в р ащ е н и я* ) ^ за п о л н ен и е четырех серединных клеток скатерти *) скат[х, y ] := fa ls e ; (*1*) 88


\

скат [х+ 1, у]: = р [2]; (*2*) скат х + 1, у + 11: = Р [3]; (*3*) скат[х, y + l j : = p [ 4 j ; (*4*)

У':’— 1; к =4; у \= У + 1 ; repeat

х : ~ = х — 1; (* переход в больший к в а д р а т * ) у : ~ ' у - (-1; (* в левую верхнюю клетку*) d \ ~=d -j- 2; for идти: = вниз to влево do for z: = 1 to d do begin (* идти сн ачала *) case идти of в н и з : у : = у — 1;

вправо :х: = х - \ - 1; вверх: у: = у - \ - 1; влево :х: = х — 1 end;

k: = k + !; (* а затем заполняется клетка *) скат [х, у}: = р \k\ end; until d = m — 1; (* печать скатерти *) for у: = m do w nto 1 do begin for x : = 1 to m do if скат [x, у | then write ( * ) else write (’ ’);

writeln

end end. Вот и вся программа. Обратим внимание на то, что при некотором изменении программы можно было бы скрутить в виде спирали любой одномерный массив независимо от того, что в нем х р а ­ нится. © Составьте процедуру для вычисления координат с к а ­ терти (см. второй способ). © © Составьте функцию для вычисления индекса (коор­ динат) решета (см. третий способ).

89


32. Совершенные числа Совершенным числом назы вается число, равное сумме всех своих делителей, м еньш и х,чем оно само.. Например: 2 8 = 1 + 2 + 4 + 7+14. Древним грекам были известны только четыре первых совершенных числа. Выдающийся греческий философ и математик Никомах из Герасы (I в.) писал: «Совершенные числа прекрасны. Однако известно, что прекрасные вещи редки, негодных ж е всюду полно». Совершенные числа чрез­ вычайно высоко ценились. Недаром в Библии сказано, что мир был сотворен за 6 дней: ведь это первое совершенное число. Д а ж е в XII в. церковь у т в ер ж д ал а, что для спасения души достаточно найти пятое число. Это число'было найдено только в XV в. Добавим, что совершенные числа еще не полностью ис­ следованы: так, неизвестно, имеется ли конечное число со­ вершенных чисел или их число бесконечно, до сих пор не найдено ни одно нечетное совершенное число (и не доказано, что таких чисел не су щ еств ует). Составим функцию, которая будет проверять, является ли заданное число совершенным: function совершенное (х :integer): boolean; v a r i, cyMMa:integer; begin сумма : = 1; for i: = 2 to x div 2 do if x mod i = 0 then сумм а: = сумма + i; совершенное : = х = сумма end Чтобы найти совершенные числа в некотором указанном интервале, следовало бы применить функцию совершенное к каж д ом у числу в этом интервале. Если интервал большой, поиск д а ж е при помощи Э В М ок а зал ся бы долгим. Поэтому мы хотим напомнить тот путь, которым мы шли при изучении простых чисел. Д л я того чтобы проверить, является ли конкретное число простым, мы составили логическую функ­ цию (см. зад ач у 26). Д л я поиска же простых чисел на промежутке [2; п\ мы прибегли к значительно лучшему способу, имеющему в качестве основания Эратосфеново ре­ шето. Оказы вается, идею Эрагосфенова решета можно при­ менить и для данной задачи. Только вместо логического массива (см. зад ач у 29) нужно взять массив чисел, а в 90


качестве его элементов — суммы делителей. Итак, мы соста­ вили такую программу: program совершенные (output ); c o n st « = 10000; (* верхняя граница интер вал а* ) var дел: array [2..«] of integer ; (* сумма делителей *)

ft, р :integer\ begin for ft: = 2 to « do

дел [ft]: = 1; (* все числа делятся на 1*) (* сумма делителей *) for ft: = 2 to « div 2 do begin

p: = f t + ft; w hile p = begin

do

C «

дел

[p]:

p: = p

+

=дел

[p] +

ft;

ft

end end;

(* совершенные числа *) for k : = 2 to n do if дел [ft] = ft then (* совершенное*)

writeln(k) end.

Выполнив эту программу, ЭВМ напечатала числа:

6 28 496 8128 © С совершенными числами тесно связаны числа М е р ­ сенна (см. зад ач у 29). Ещ е Евклид д оказал , что каждое число, которое можно представить в виде произведения 2Р'~1(2Р— 1), является совершенным числом при условии, что 2Р— 1 — простое число. Л. Эйлер (Euler, 1707— 1783) доказал, что по формуле Евклида можно найти все четные совершенные числа. (К ак выглядят нечетные совершенные числа и существуют ли они вообще, неизвестно до сих пор.) Таким образом, если у нас есть простые числа Мерсенна, нетрудно находить и совершенные числа. Составьте программу, которая найдет все совершенные числа Мерсенна, помещ ающиеся в ЭВМ. Быть может, среди них окаж ется и пятое совершенное число?


33. Дружественны е числа Есть легенда, которая гласит, что, когда П и ф агора спро­ сили, что такое д р уж б а, он ответил: «220 и 284». Так возник термин «дружественные числа». Дружественными числами являются д ва натуральных числа, таких, что к а ж д о е из них равно сумме всех натуральных делителей другого, исключая само это другое число. Действительно, 220 и 284 являются дружественными числами, поскольку сумма делителей чис­ л а 220 — это 1 1-2 + 4 + 5 + 1 0 + 1 1 + 2 0 + 22 + 44 + 5 5 + 1 1 0 = 284, а сумма делителей числа 284 — это | + 2 +

4 + 71 +

142** 220.

Л1 и-ы 1п т т . н га кос определение дружественных чисел: . \мм i И'сч I!• ци|с.мсП одного и другого такого числа равна $ ( VММ нОиик чн гt*Л. | | . ( ир|«|иж(41ии иском 220 и 2Н4 были единственной ................ .. пар..и т 11\ и,ссIиснпы,ч чисел. Только и середи­ не V,\ „ (■|мт. | )мvt чн, та и. I 000 000, нашли 42 пары друI I I и. Ii mi ч 'и и i I 1I;. j i Iи н о м у в средние века полагали, *1 1. 1 I I пн I m u I ними числами укрепляют любовь. Чи ги 1ем искан, и указанном интервале дружественIII h чн, 1.1 гем /ке способом, что и совершенные числа. Мы ta I с и......лI,.чуем некоторую часть программы совершенные, .1 именно ту, и которой отыскивается сумма делителей. Законченная программа выглядит следующим образом: pro g ra m дружественные (output ); const п = 10000; (* верхняя граница и нтер вал а* ) v a r д е л : a r r a y [2..п) of integer-, (* сумма делителей*) i, /, k, р: integer-, begin for k: = 2 to n do (* все числа делятся *) дел [k]: = 1 + / е ; (* на 1 и самих себя *) (* сумма делителей *) for k : = 2 to п div 2 do begin

p: = k - \ - k ; while begin

do

дел[р}: = д е л [p\-\-k; p:=p + k end end; (* дружественные числа *) for i : = 2 to n — 1 do 92


for /': = / + 1 to n do if (дел [ / ] = / + /) a n d (дел [ / ] = / + /) then (* пара дружественных чисел *) writeln (г, /) end. Выполнив эту программу, Э В М н аш ла пять пар д р у ­ жественных чисел: 220 1184 2620 5020 6232

284 1210 2924 5664 6368

© Составьте программу, которая н аш л а бы в интервале [1; 1000] число, имеющее больше всего делителей.

34. Цифры Взглянув на число, мы сразу можем сказать, из каких цифр оно состоит. Так что для человека нетрудно опреде­ лить, из каких цифр состоит число. Но для ЭВМ сделать это непросто. Мы пользуемся десятичной системой счисле­ ния, а в машинной памяти хранятся числа в иной записи, чащ е всего в двоичной системе. (Относительно систем счисле­ ния см. з ад ач у 43.) Нет прямого соответствия между привыч­ ными нам десятичными цифрами и цифрами прочих систем счисления. К тому же как в основах математики, так и в теории программирования число рассматривается как одно неделимое значение. А тот факт, что число состоит из цифр, обусловлен только принятым у нас способом записи чисел. С разу определить, из каких цифр состоит число, мы не можем ни пользуясь языком П аскаль, ни при помощи какихлибо других языков программирования. Во всех язы ках программирования с числами можно производить определен­ ные арифметические операции. С их помощью придется вы­ разить и действия над цифрами. Установить, из каких цифр состоит число, можно, исполь­ зуя операцию деления. Например, если известно, что число х является трехзначным, то его цифры а, b и с мы сможем найти следующим образом:

а: = х div 100; b : ==x div 10 mod 10; с: = х mod 10. 93


Составить число из цифр мы можем при помощи операций умножения и сложения. Например:

х : = 100*а + 10 *Ь + с. Опишем некоторые универсальные функции, имеющие дело с цифрами. Будем рассматривать только натуральные числа. Поэтому параметр п всех трех приведенных ниже функций — натуральное число. 1. Сколько цифр в данном числе function числоцф (п: integer): integer; (* число цифр *) v a r k: integer; begin k:=0;

repeat

k:=k+U n: = n div 10 until n = 0; числоцф: = k

>J

end 2. Нахождение суммы цифр числа function суммацф ( п : integer): integer; (* сумма цифр *) v a r s: integer; begin s : = 0;

repeat s : = s + n mod 10; n : = n div 10 until n = 0;

суммацф: — s end 3. Запись числа в обратном порядке Эта функция записы вает заданное число в обратном порядке (справа налево). Например, число 50 410 будет з а ­ писано как 1405 (незначимые нули вначале опускаются). function наоборот (п:integer):integer; (* то ж е число, записанное в обратном порядке *) v a r a:integer; begin а : = 0; repeat а : = а * 1 0 + га mod 10;


n: = n div 10 until п = 0; наоборот: — а end

Все три приведенные функции осущ ествляют простые действия, с которыми мы легко справляемся и без помощи ЭВМ. Однако они потребуются при составлении программ для многих интересных зад ач с цифрами. © Числа, одинаково читающиеся и слева направо, и справа налево, назы ваю тся палиндромами. Например, чис­ л а 42 324 или 1331 — палиндромы. Составьте функцию, которая будет проверять, является ли заданное число п а ­ линдромом. © © Числа, запись которых состоит из двух одина­ ковых последовательностей цифр, назы ваю тся симметрич­ ными. Например, 357 357 или 17 421 742 — симметричные числа. Составьте функцию, которая будет проверять, яв л яе т­ ся ли заданное число симметричным. 0 © 0 Если мы сложим все цифры какого-либо числа затем — все цифры найденной суммы и будем повторять это много раз, мы наконец получим однозначное число (цифру), называемое цифровым корнем данного числа. Н а ­ пример, цифровой корень числа 561 равен 3 (5 + 6 + 1 = -12; 1 + 2 = 3). Составьте функцию для нахождения числового корня на­ турального числа.

35. Автоморфные числа Автоморфным называется такое число, которое равно последним цифрам своего квадрата. Например: 52 = 25; 252= 625. Д л я нахождения всех автоморфных чисел в интерва­ ле [пг; п] составим такую программу: p ro g ra m автоморф (input, output)-, v a r m, n, (* заданный интервал *) x, (* пробное число *) d: integer'-, (* 10, 100, 1000, begin

read (m, n); d: = 10; for x: = m to n do 95


begin while d ^ x do d : = d * 10; if x*x mod d = x then

writeln (x, x*x) end? end. Выполнив приведенную программу, ЭВ М н аш ла в интер­ вале [1; 1000] следующие автоморфные числа (в первом столбце напечатаны автоморфные числа, во втором — их к в а д р а т ы ); 1 5 6 25 76 376 625

1 25 36 625 5776 141 376 390 625

© Приведенная программа проверяет подряд все числа в интервале [т\ п\. Однако некоторые числа не надо д аж е проверять: итак ясно, что они не являю тся автоморфными. Это зависит от последней цифры. Чтобы число было автоморфным, оно долж но оканчиваться либо на 1, либо на 5, либо на 6. Убедитесь в этом. Поэтому программу можно усовершенствовать — уменьшить число переборов. Усовер­ шенствуйте составленную программу. . © © Аналогичным образом мы могли бы определить «кубические» автоморфные числа. Они равны последним цифрам своих кубов. Например: 63== 216; 513 = 132 651; 763 = 438 976. Составьте программу для нахождения «кубических» автоморфных чисел в интервале [ т ; п].

36. Нумерация книжных страниц В книге п страниц ( п — входное данное). Составим функцию, которая буДет находить, сколько цифр понадобится д л я ■того, чтобы занумеровать все страницы этой книги. Сколько нужно цифр для всех й-значных ( k = l , 2, 3, ...) чисел, мы можем выяснить, р ассуж д ая таким образом: для однозначных чисел требуется 9 X 1 цифр 96


9 0 X 2 цифр 9 0 0 X 3 цифр

для двузначных для трехзначных

для /г-значных чисел требуется 900 ... 00Х& цифр k — \ нулей

ИЛИ

(999 ... 9 — i ООО ... 00 + 1) X k цифр к цифр

к — 1 нулей

Иначе говоря, следует вычислить значение выражения

(наибольшее — наименьшее + 1) X k , где k обозначает число цифр ( k = \ , 2, 3, ...), а перемен­ ные наименьшее и наибольшее — соответственно наименьшее и наибольшее k -значное число. Д л я того чтобы установить, сколько цифр в числе п, применим функцию числоцф, опи­ санную в зад ач е 34: function страницы (п: integer): integer; (* сколько необходимо цифр, *) (# чтобы пронумеровать книгу из п страниц *) v a r k, (* количество цифр *) (* во всех fe-значных числах *) наименьшее, (* наименьшее и *) наибольшее, (* наибольшее fe-значное число *) сколько

:integer;

begin сколько

: =0;

наименьшее : = 1; наибольшее'. = 9 ; for k'. = \ to числоцф (п) — 1 do begin сколько

: — с к о л ь к о + (наибольшее — наименьшее +

+ 1 )*&;

наименьшее : = наименьшее* 10; наибольшее: = наименьшее*\0 — 1 end;

страницы: =

сколько

+ (и — наименьшее + \ )*числоцф(п)

end Составленные функции можно включить в программу (или процедуру) двумя способами: 1) функция числоцф является внешней функцией по отношению к функции стра­ ницы, т. е. обе упомянутые функции включаются в програм­ му параллельно; 2) функция числоцф является внутренней, включаемой внутрь функции страницы. 4 В. А. Д агене


© Составьте функцию для решения обратной задачи: для нумерации страниц книги потребовалось k цифр ( k — входное д а н н о е ). Сколько страниц в книге? Если указанное число цифр не может служить д ля нумерации какого-либо количества страниц, то результат функции считайте р а в ­ ным 0.

37. Счастливые троллейбусные

билеты

Номера троллейбусных билетов представляют собой шес­ тизначные числа. Счастливым считается тот билет, у кото­ рого сумма первых цифр р ав н а сумме трех последних цифр. Например, билет 627 294 считается счастливым, так как 6 + 2 + 7 = 2 + 9 + 4 = 15. Условимся, что номера билетов п рин ад леж ат промеж ут­ ку [100 000; 999 999]. М ожно придумать много интересных зад ач , связанных с номерами счастливых билетов. Опишем одну из них, предоставив читателям самостоятельно решить еще несколь­ ко таких задач. П реж де всего составим очень простую функцию, которая будет устанавливать, является ли рассматриваемое число счастливым: function счаст ( х '.integer):boolean-, (* является ли число счастливым *) function суммацф (п: integer): integer; (* сумма цифр *) ... (см. зад ач у 34) begin (* счаст *) if ( х с 100 000) ог (х > 999 999) then (* число не является шестизначным *) счаст: = false else счаст: = суммацф (х div 1000) = суммацф [х, mod 1000) end Д ал ее мы составим программу (в ней мы используем и функцию счаст) д ля решения следующей задачи: найдите все номера счастливых билетов, такие, что из них можно извлечь натуральный корень какой-либо (превышающей 1) степени. Например, У?20 801 = 8 4 9 . Эту зад ачу можно решить, перебирая по очереди все шестизначные числа и проверяя, удовлетворяют ли они условию задачи: является ли число счастливым и и звл ека­ ется ли из него корень 2, 3, 4-й и т. д. степени. Шестизначных 98


чисел много, да и счастливых среди них немало (более 50 ООО — [20]) . Таким образом, вычисление здесь будет д о л ­ гим. Поищем какой-нибудь другой способ решения. Попро­ буем пойти в обратном направлении — поочередно возводить натуральные числа то в одну, то в другую степень. Если по­ лученное число оказы вается шестизначным, то надо про­ верить, является ли оно счастливым. Самое большое из под­ вергаемых проверке натуральных чисел долж но быть меньше чем 1000, т а к - к а к в противном случае его квадрат будет иметь более шести знаков: 1ООО2 > 9 9 9 999. Поскольку к а ж ­ дое число надо будет возводить в степень не более чем 20 раз (2020 — это у ж е семизначное число), то для решения зад ач и этим способом надо рассмотреть около 1000-20 = 20 000 чисел (в действительности намного меньше, т ак как, чем больше число, тем меньше раз придется его возводить в степень). Приведем законченную программу: p ro g r a m счастливые (output); v a r k, (* корень *) р, (* степень корня *) х : integer; (* кандидат в счастливые числа*) function с част (x'.integer): boolean; (* яв л яется ли число счастливым *) (* функция данной задачи *) begin (* счастливые *) for k : — 2 to 999 do (*1.000*100Q > .9 9 9 999*) begin (* пробные натуральные числа *) x:~k*k; («степени которых суть*) р;=2; (* шестизначные числа *) while х ^ 9 9 9 999 do begin if счаст (х) then

writeln(x, р, k); х : = x*k; р : —р -И end

'

end end. © Составьте программы, печатающ ие все такие номера счастливых билетов, которые равны: а) квад рату какого-либо натурального числа; б) кубу какого-либо натурального числа; в) кв ад рату какого-либо натурального числа и одновре­ менно кубу какого-либо другого натурального числа. 99


© © Составьте программу для нахождения всех номе­ ров счастливых билетов, у которых сумма первых (послед­ них) трех цифр, будучи возведенной в какую-либо степень, равна номеру счастливого билета.

38. Сумма кубов цифр Существуют натуральные числа, равные сумме кубов своих цифр. Таково, например, число 370, ибо З3 + 73 + 03 = 370. Составим программу для нахождения всех таких чисел. Эта за д а ч а интересна тем, что позволяет найти все числа, обладаю щ ие названным свойством. Но ведь ЭВ М не может проверить все числа. Значит, прежде всего надо попытаться определить интервал, в котором можно надеяться встретить такие числа. Возможно, он окаж ется конечным. С ам а я б ольш ая цифра — это 9. Поэтому с а м а я б ольш ая сумма кубов цифр однозначных чисел — это 93 = 729, д в у ­ з н а ч н ы х — 93+ 93= 1458, трехзначных 93- f 93-|~ 93= = 2 1 8 7 , четырехзначных — 93 + 93 + 93-|-9 3 = 29 16. С ам ая больш ая сумма цифр пятизначных чисел — это 3645, т. е. четырехзначное число. Значит, пятизначные и более чем пятизначные числа не удовлетворяют указанном у условию, и поэтому можно их не проверять. Точно так же можно не проверять и четырехзначные числа, превосходящие 2916. Еще более вникнув в суть дела, увидим, что из чисел, мень­ ших чем 2916, но больших чем 2000, наибольшую сумму ку­ бов цифр имеет число 2899. Эта сумма равн а 23 + 83 + + 93 + 93= 1978, т. е. меньше чем 2000. Следовательно, числа, превосходящие 2000, нас не интересуют — достаточно проверить числа в промежутке [ 1; 2000]. Теперь приведем составленную программу: p ro g r a m кубы (output)-, v a r х, (* пробное число *) п, (* последняя цифра *) р, (* число без последней цифры *) s\ integer-, (* сумма кубов цифр *) begin for ;с: = 1 to 2000 do begin s: = 0 ; p: — x\

while p > 0 do (* нахождение сум мы*) (* кубов цифр числа х *) 100


begin

n: — p mod 10; p: = p div 10; s: = s-\-n*n*n end; if x = s then writeln(x ) end end. Выполнив эту программу, ЭВМ н ап ечатала следующие числа: 1 153 370 371 407 © Составьте программу, которая будет находить все натуральные числа, равные кубу суммы своих Цифр.

39. Числа Армстронга Число, состоящее из п ( п > 1) цифр, назы вается числом Армстронга, если сумма его цифр, возведенных в п-ю сте­ пень, равна самому этому числу. Например, числами Арм­ стронга являю тся 153 и 1634, так как 1 5 3 = 13 + 53 + 33; 1634 = 14+ 64+ З4 + 44. Составим программу, которая будет находить все ге-значные числа Армстронга (п — входное данное, причем г а < 10). Чтобы программа была более экономной, цифры от 0 до 9 мы возведем в нужную степень один раз и результаты сохраним в массиве v a r с т е л : a r r a y [0..9] of integer Составим схему программы: p ro g r a m армстронг [input, output ); v a r степ: a r r a y [0..9] of integer ; n, (* исходное данное *) x, (* пробное n -значное число *) mm, (* наименьшее n -значное число *) max'.integer; (* наибольшее n -значное число*) begin

read(n); 101


массив степ заполняется n -ми степенями цифр О, 1, 2, ..., 9

находятся наименьшее и наибольшее д-значные числа: min и max for x : = m i n to max do устанавливается, какие цифры образуют пробное число х, и находится сумма п-х степеней этих цифр; если эта сумма равна х, значит, х — число Армстронга . end Д етализируем действия, записанные в прямоугольниках. Массив степ можно заполнить следующим образом: for k: = 0 to 9 do begin

степ [k] \ — k\ for / ; = 2 to n do

степ [k]: — степ [k)*k end Д ействия второго прямоугольника элементарны. Р а с ­ смотрим третий прямоугольник. Д л я того чтобы установить, какие цифры образуют пзначное число, и найти сумму п-х степеней — сумма, следует написать такой цикл:

р : = х ; сумма-. = 0 ; for /: = 1 to п do begin

сумма : = сумма + степ [р mod 10]; р: = р div 10 end Соединив все части, получим законченную программу: p ro g r a m армстронг (input, output)-, v a r степ: a r r a y [0..9] of integer; n, (* исходное данное *) 10 2


х, min, max, сумма,

(* пробное n -значное число *) (* наименьшее n -значное число *).■ (* наибольшее ге-значное число *) (* сумма п-х степеней *) (* цифр числа х *)

k, I, р '.integer; begin

read(n); (* заполняется массив степ *) for k : = 0 to 9 do begin

степ\к\: =-к; for /: = 2 to ri do CTen[k]: = cren[k}*k end; (* находятся наименьшее и наибольшее *) (* я-значные числа: min и m ax*) m i n : = 1; for /: = 1 to n — 1 do m i n : = min* 10; m a x '. = min * 10— 1; (* испытываются все n -значные числа *) for x : = min to max do begin

p: = x ; сумма : = 0 ; (* находится сумма n~x степеней *) (*цифр числа х *) for I : = 1 to п do begin сумма: = сумма-{-степ [р m od 10]; р : = р div 10 end; if сумма = х then (*х — число А рмстронга*)

writeln(x) end end. При входном данном п = 5 ЭВ М н ап ечатала такие числа Армстронга: 54 748 92 727 93 084 0 Обобщите данную программу таким образом, чтобы она напечатала все числа Армстронга, меньшие миллиарда. юз


0 © Составьте программу, которая обнаруж ит само большое число Армстронга, еще помещ аю щееся в ЭВМ.

40. Квадраты, состоящие из разных цифр Б. Кордемский пишет [12], что среди всех десятизн ач ­ ных натуральных чисел, у которых все цифры различны, имеется 10 полных квадратов, т. е. 10 таких чисел, из ко­ торых можно извлечь точный квадратный корень. Было бы интересно исследовать меньшие л-значные числа ( « С 10). Составим программу, Которая д о л ж н а найти все л-значные (п — входное данное, причем 2 ^ л ; £ С 9 ) числа, о б л а ­ даю щие названным свойством: число долж но состоять из разных цифр и быть полным квадратом какого-либо н ату­ рального числа. Один из самых интересных моментов составления про­ граммы — установить, состоит ли рассматриваемое число из различных цифр. Д л я этого мы составим логическую ф унк­ цию различные. Алгоритм таков: к а ж д а я цифра рассм атри ­ ваемого числа вносится в множество — если встретится цифра, которая уж е имеется в множестве, то ясно, что цифры рассматриваемого числа не будут различными, и это число отбрасывается. Я вляется ли рассматриваемое п-значное число (п — 2, 3, ..., 9) полным квадратом какого-либсс натурального числа, можно проверить двумя способами. Первый способ: пере­ брать все л-значные числа, цифры которых различны, и извлечь из них квадратный корень — если извлекается точ­ ный корень, то рассматриваемое число обладает сформулиро­ ванным выше свойством. Второй способ: перебрать все нату ­ ральные числа, квадраты которых представляют собой л-значные числа, и тогда проверить, состоят ли квадраты из р а зл и ч ­ ных цифр. Эта зад ач а реш ается аналогично зад ач е 37. Нетрудно понять, что второй способ намного превосходит первый (проверяется меньше чисел). Поэтому мы вы би ра­ ем второй способ. П р еж де всего надо установить, у ка ко ­ го наименьшего натурального числа квад рат представляет собой л-значное число. Д л я этого составим функцию

тткЬ. Самое большое число тахкв, квад рат которого пред­ ставляет собой л-значное число, можно найти, применив функцию т1пкв:

т а х к в : = Тпткв (п + 1) — 1 104


Составив все названны е функции и соединив их, получаем законченную программу: p ro g r a m квадраты (input, output)-, v a r х, (* пробные числа, *1 кв, (* квадраты которых суть *). п, (* л-значные (п — исходное *) (* данное) числа *) т а х к в '.integer; (* наибольшее пробное число*) function различные (х:integer): boolean; (* состоят ли числа из различных цифр *) var s : s e t of 0..9; цифра: 0..9;

р а з л : boolean; begin s : ==[ ]; разл: — true; repeat цифра: — x mod 10; if цифра in s then разл: = false else s : = s -{-[цифра]; x: — x div 10 until (x = 0) or not разл;

различны е: — разл end; function minKe (n '.integer):integer; (* находится наименьшее натуральное число,*) (* квад рат которого — ге-значное число *) v a r nmin, (* наименьшее л-значное число *)

i: integer; ■ function sqroot (x:integer)'.integer; (* квадратный корень из натуральных чисел *) ... (см. з ад ач у 9) function sqrootmod ( х '.integer):integer; (* остаток при извлечении квадратного корня *) ... (см. зад ач у 9) begin (* minKe *) n m in ; = 1; for i : = 2 to n do if

n m i n : = n m in * \0 ; sqrootmod (пт1п)-ф0 then minKe: — sqroot (nmin)-\-\ else minKe: — sqroot (nmin)

end; begin (* квадраты *) r e a d (n);

т а х к в : — т т кв (n + 1) — 1; for x : = minKe (n) to тахкв do

, 05


begin

к в : = x*x; if различные (кв) then writeln (кв, = , x : l , * , x : l ) end end. © Составьте функцию, которая будет вычислять, сколько различных букв содержится в заданном слове. © © Составьте аналогичную программу для н ах о ж д е­ ния кубов, состоящих из различных цифр.

41. 1 ! + 4 ! + 5! = 145 Б. Кордемский [12] указы вает одно интересное число 145, которое равно сумме факториалов своих цифр: 145 = 1! + + 4 ! + 5!. Он пишет, что неизвестно, есть ли ещ е такие числа, удовлетворяющие названному условию. Самый простой способ поиска таких ч и с е л — проверять все числа подряд. Но ведь множество натуральных чисел бесконечно, так что мы не смогли бы таким образом найти все возможные решения. Однако в данном случае можно заметить, что долж но существовать такое число, превосхо­ дить которое интересующие нас числа не могут. Тогда мы составили бы программу для проверки конечного числового интервала. Будем р ассуж д ать следующим образом. Ф акториал самой большой цифры (9) представляет со­ бой шестизначное число. Значит, среди шестизначных чисел еще могут встретиться искомые числа. М акси м альная сум­ ма ф акториалов цифр семизначного числа — это 7 X 9 ! = = 2 540 160. Мы видим, что это семизначное число неве­ лико. М акси м альная сумма ф акториалов цифр восьмизнач­ ного числа т а к ж е представляет собою всего лишь семи­ значное число. Следовательно, достаточно проверить числа, меньшие чем 2 540 160. Составляем программу: p ro g r a m суммафащ (output); const, тахп = 2 540 159; type интервал = 0..9; (* цифры *) v a r фциф: a r r a y [интервал] of integer; (* факториалы цифр искомых чисел *)

k : интервал; п '.integer; (* пробное число *) function сумф (п '.integer):integer; 106


(* сумма факториалов цифр числа п *) v a r сумма:integer-, begin (* сумф *) сумма: = 0 ; repeat сумма: = сумма-\- фциф {п mod 10]; п : = п div 10 until п = 0;

сумф: = сумма end; (* сумф *) begin фциф [0]; = 1; (* запоминаются факториалы циф р*) for k: = \ to 9 do фциф f/г]: = ф ц и ф [k — 1]*/г; for п: = 1 to тахп do if сумф (n) = n then writeln (n) end. Д л я того чтобы программа была экономной, факториалы цифр (от 0 до 9) вычисляются один раз и сохраняются в массиве фциф. Второй цикл в основной части программы повторяется более чем 2,5 млн. раз. В цикле содержится обращение к функции сумф , а в этой функции — еще один цикл. Так что д а ж е быстродействующая ЭВМ долго билась бы с этой программой, поэтому стоит поискать способы уско­ рения вычислений. Попытаемся уменьшить число повторений в основной части программы. Быть может, какие-то числа можно отбро­ сить заранее? Посмотрим, как изменяется сумма факториалов цифр у чисел, принадлеж ащ их одному десятку от 0 до 9. В одном случае и у числа ... 0, и у числа ...1 — сумма факториалов цифр одинакова, поскольку факториал и нуля, и единицы равен единице. Ещ е в одном случае — при замене числа ... 1 числом ... 2 — сумма факториалов цифр увеличивается на единицу — на столько же, на сколько увеличилось и само число. Во всех прочих случаях эта сумма растет быстрее чем число. И з этого мы можем сделать вывод, что если сумф ( п ) > п - \ - \ , то в данном десятке точно не найдется чисел, превышающих п (т. е. таких, у которых последняя цифра больше последней цифры числа п, а все остальные цифры совпадают с цифрами числа п) и удовлетворяю щих условию задачи. Составим новый вариант программы, в котором будет учтено рассмотренное свойство чисел. 107


p ro g r a m суммафакц (output)-, const m a x n = 2 540 159; type интервал = 0..9; (* цифры *) v a r фциф: a r r a y [интервал] of integer-, (* ф акториал цифр искомых чисел *)

k : интервал-, п, s, п п : integer-, function сумф

(* пробное число *) (* сумма факториалов цифр *) (* десятки *)

(п:integer):integer-,

(* сумма факториалов цифр числа п *) v a r c y M M a : in t eg e r \ begin (* сумф *) сумма: = 0 ; rep eat сумма: = сумма -\-фциф [п mod 10]; п: — п div 10 until n = 0; сумф: = сумма end; (* сумф *) begin (* суммафакц *)

фциф [0]: = 1; (* запоминаются факториалы циф р *) for k : = 1 to 9 do (* цифр *) фциф [k]: = фциф [k — \}*k\ for n n : = 0 to maxn div 10 do (* цикл для (* чисел *) begin п : = п п * 10;

д есяти*)

s: = с у м ф (п)\ repeat if s = n then writeln (n)\ n : = я + 1; (* корректируется сумма факториалов цифр *) s : = s — фциф !(>г — 1) mod Щ-\-фциф [п mod 10] until ( s > n ) or (* в десятке не удастся найти*) (* искомых чисел *) (п m od 10 = 0) (* закончился д есяток*) end end. Во втором варианте программы меньше содержится и обращений к функции сумф. Обращ ение к ней происходит только во внешнем цикле «десяток». Во внутреннем цикле сумма факториалов цифр пересчитывается путем коррекции предыдущей суммы. Выполнив эту программу, ЭВМ н ап ечатала результаты: 108


1 2 145 40 585 0 Составьте программу, ещ е быстрее производящую вычисления, необходимые для данной задачи. Используйте в ней закон перестановки слагаемых (от перемены мест слагаемых сумма не меняется). Поэтому достаточно прове­ рить, состоит ли сумма факториалов цифр рассматриваемого числа из тех же цифр, что и само число. Если да, то число, равное сумме факториалов цифр, удовлетворяет условию задачи, если нет — то условию задачи не удовлетворяют и все прочие числа, которые получаются из рассматриваемого числа путем перестановки цифр. Например, 2! + 4 ! + '5 ! = 146. Из цифр 2, 4 и 5 нельзя составить число, равное сумме ф акториалов этих цифр, а именно числу 146. Следовательно, все трехзначные числа, состоящие из цифр 2, 4 и 5 (245, 254, 425 и т. д .), не удовлетворяют условию задачи.

42. Наименьшее число не всегда мало Было бы интересно найти наименьшее число, оканчиваю ­ щееся на пять, такое, что, если перенести его последнюю цифру в начало, число увеличится в пять раз. Д л я многих зад ач , в которых требовалось найти числа, обладаю щ ие каким-либо свойством, мы составляли очень простые •программы — проверяли все числа в заданном ин­ тервале и рассчитывали на быстродействие ЭВМ. Поскольку здесь требуется найти только одно минимальное число, удовлетворяющее указанному условию, то возникает идея проверять подряд все числа, оканчиваю щ иеся на пять: 5, 15, 25, 35, ...— до тех пор, пока мы не найдем то, которое нужно. К сожалению, ЭВМ будет биться над решением долго и тщетно —- искомое число столь велико, что д а ж е са м ая быстрая машина искала бы его невероятно долго. Поэтому приходится искать другое, лучш ее решение. Предположим, что цифры искомого числа — это х\, х 2, ..., х„. Мы знаем, что х„ = 5. Поэтому условие задачи можно записать так: 109


X i X 2X 3. . . X n - \

X 5

5 5

X[X2X3...Xn - i

Попытаемся поискать отдельные цифры. Умножив любую цифру хi ( х ; > 1 ) на пять, получим д ву ­ значное число ab, состоящее из цифр а и Ь. Поскольку посл едняя цифра умножаемого числа известна, получим ab = = 25, т. е. а = 2 и Ь = 5. Таким образом, последняя цифра произведения «в уме» такова: хп- \ = 6 = 5. Но эта цифра тем самым является и предпоследней цифрой множимого. С л е­ довательно, теперь можно умножить на пять уж е ее и по­ лучить новую, предпоследнюю цифру произведения х п- 2. Так надо ум н ож ать до тех пор, пока мы не получим ab — Ъ. Таким образом, д ля получения всех цифр результата мы должны создать процедуру. Поскольку мы получаем цифры в обратном порядке (единицы, десятки, сотни и т. д .), то вначале надо их запомнить, а затем напечатать в порядке, обратном по отношению к тому, в котором они были полу­ чены. Поэтому мы составляем рекурсивную процедуру: procedure р (а,

(* в уме *)

b ’.integer)',(* число *) v a r a b : 0..99; begin ab: = 6 * 5 -fa; if а Ь ф Ъ then p (ab div 10, ab m od "10); write (ab m od Ш: 1) end Обращ ение к этой процедуре долж но происходить так:

р (0,5). Второй параметр показывает, что число оканчи­ вается на пять, а первый — что при выполнении умножения «в уме» ничего нет, так как умножение начнется с последней цифры. Приведем результат, напечатанный ЭВМ: 510204081632653061224489795918367346938775. Это фактически не искомое число, а число, которое по­ лучается из искомого перенесением в начало последней цифры. Попытаемся найти искомое число, пойдя в обратном н а ­ правлении: будем искать наименьшее число, которое умень­ шится в пять раз, если его первую цифру перенести в ко­ нец числа. но


Цифры числа, получающегося в результате, можно пе­ чатать сразу же, не запоминая их, поскольку цифры частного мы получаем в обычном порядке — слева направо. Поэтому можно составить нерекурсивную программу. p ro g r a m деление (output ); v a r a b : 0..99; b: 0..9; begin ab: = 5; repeat b : = a b div 5; write (6:1); a b : = ( a b mod 5)* 10 + fr until ab = 5 end. М еж ду прочим, этот же результат нетрудно получить и без помощи ЭВМ. Попытайтесь сделать это. © Составьте программу для нахождения двух наимень­ ших чисел, которые начинаются на пять и из которых, пере­ неся первую цифру в конец, мы получим новое число, в пять раз меньшее, нежели искомое. © © Составьте программу, в которой входное данное — число п в интервале [2; 9], а результат — наименьшее число, у которого первая цифра — п и из которого, перенеся первую цифру в конец, мы получим новое число, в п раз меньшее, нежели искомое.

43. Двоичные числа В настоящее время почти во всем мире используется десятичная система счисления. Когда-то использовались и другие системы. Их реликты можно обнаружить и сейчас. Сюда относится шестидесятеричное исчисление времени (в часе 60 мин, в минуте 60 с). На основе различных систем счисления образованы старинные единицы измерения (см. зад ачу 53). Вольтер, например, пишет, что шведский ко­ роль Карл XII хотел ввести двенадцатеричную систему, по-видимому, ж е л а я таким образом увековечить свой д и н а с ­ тический номер. Д есятич н ая система счисления удобна д ля начального обучения арифметике при помощи счета на пальцах, по­ скольку на обеих руках вместе у нас 10 пальцев. А для счета при помощи машин значительно удобнее двоичная система. Ведь многие физические объекты имеют два четко разли чи ­ ill


мых состояния. Они используются для изображ ения цифр двоичной си с т е м ы — 0 и 1. Например, если по проводу те­ чет электрический ток — единица, если не течет — нуль; если железный сердечник намагничен — единица, если не намагничен-— нуль и т. д. . Хотя почти все ЭВМ оперируют двоичными числами, про­ граммист их не видит, так как десятичные числа, которые записываю тся в исходных данных или в тексте программы, ЭВМ автоматически переводит в двоичные, производит с ними действия, а полученные результаты опять переводит в десятичную систему счисления. Как числа переводятся из одной системы в другую, можно прочесть в книгах Г. Бермана и А. Д ом ор яд а [9, 10]. Число п можно однозначно выразить в р-ричной системе счисления при помощи цифр гк, Г к - 1, ГЩ г0 так:

П = р кГк + рк~ [Г к - 1 +

... + p r v+ r 0

и зап и сать его таким образом: П= ГкГк- \ ....... Г|Г0 Например, число 25 можно записать десятичными ц иф ­ рами: , 10-2 10-5. Это же число цифрами двоичной системы можно записать так:

2 -- 1+ 23-1 + 22-0 + 2 ' •0 + 2°• 1.

Поэтому в двоичной системе оно записывается таким образом: 11001. Составим процедуру для ввода натурального числа, з а ­ писанного в двоичной системе: p rocedu re чтение (var п '.integer)', var с : char ; begin repeat ■■(* пропускаются все символы*) read (c)(* перед числами *) until (с = ’ f ); п : —0; while (c = ’ o ’ ) or (c = ’f ) do begin n : = n*2; if c = 1 then h : = n - \ - 1; read (c) end end 112


Составим процедуру для печати заданного натурального числа в двоичной системе: procedure печать (п: integer)-, begin if r a > 0 then печать (п div 2); if odd (n) then write ( 1 ) else write ( 0 ) end Цифры двоичной системы получаются при делении з а д а н ­ ного числа на д ва (основание двоичной системы). Они представляю т собой последовательность остатков от деления на два, расположенную в обратном порядке. Д л я того чтобы последовательность цифр была напечатана в обратном по­ рядке, используется рекурсия. © Составьте процедуру, которая читала бы число, з а ­ писанное в двоичной системе, и печатала бы его так ж е в двоичной системе счисления. © © Составьте программу, которая прочитывала бы два целых числа, записанных в двоичной системе, ск лады вал а их и результат печатала бы так ж е в двоичной системе.

44. Шестнадцатеричная система При проектировании или эксплуатации вычислительной техники приходится иметь дело и с двоичными числами. Д л я человека они неудобны тем, что для представления числа требуется длинный ряд цифр. Например, число 1990 в двоичной системе записы вается так: 11111000110. Такое число трудно как прочесть, так и запомнить. Числа 2"-ричных систем удобно представлять в виде последовательности единиц и нулей, переводя в число двоичной системы к а ж ­ дую цифру 2"-ричной системы. (К а ж д а я цифра числа 2"-ричной системы соответствует п цифрам двоичной системы.) Например, число, записываемое в четверичной системе как 133 002, можно записать при помощи диад:

И,

00

1

00

10 3

3

0

0

2

а восьмеричное число 3702 — при помощи триад: 011

111

000

010

3

7

0

2 из


Поэтому в вычислительной технике часто используется восьмеричная, а т а к ж е шестнадцатеричная системы счисле­ ния. Поскольку основание шестнадцатеричной системы пре­ вышает 10, то для цифр 10, 11, 12, 13, 14 и 15 надо ввести специальные обозначения. Принято обозначать их при по­ мощи букв А, В, С, D, Е, F. Составим программу для ввода и печати ш естнадцате­ ричного числа так, чтобы его цифры были представлены в виде тетрад: p ro g r a m печать (input, output)-, (* шестнадцатеричные числа напечатать *) (* в двоичной системе группами по четыре цифры *) v a r с : char\ begin rep eat (* пропускаются все символы *) read (с) (* перед числами *) until с in f’f ..’9 ’, ’А ’, ’В’, ’С ’, ’D ’, ’Е ’, ’Р.’]; while с in [O ’ ..’9 ’, А’, В ’, ’С ’, D ’, Е ’, F ] do begin case с of ’о write 0000’); ’1 write 000 f) ; ’2 write 0010’); ’3 : write ООП’); 4 : write 0100’); ’5 : write 0101); ’6 : write 0110’); ’7 : write 0111’); ’8 : write 1000); ’9 : write 100 Г); ’А : write 1010); ’В : write ’1011’); ’С : write ’ 1100’); D : write ’l 101); ’Е : write ’1110’); ’F ; write ’l l l l ’) end; * case *) ' write ( ’); (* промежутки между группами по четыре цифры *) read (с) end end. 114


© Составьте процедуру для: а) ввода; б) печати — заданного шестнадцатеричного числа. © © Составьте программу, которая читала бы предъяв­ ляемое двоичное число и печатала бы его в ш естнадцате­ ричной системе.

45. Палиндром Слово, ф р а за или стихотворная строка, одинаково чи­ таемые слева направо и справа налево, называю тся палин­ дромами. Например: ИДИ ПОТО П И С К А ТЬ ТАКСИ То ж е самое можно ск азать и о числах. Палиндромами являются, например, числа 121 1331 42 524 Является ли десятичное число палиндромом, человек может заметить сразу. Было бы интересно знать, является ли конкретное число палиндромом в какой-нибудь другой системе счисления. Например, число 45 является палиндро­ мом в двоичной (101101) и в восьмеричной (55) системах. (Относительно систем счисления см. зад ач у 43.) Составим программу для нахождения палиндромов з а ­ данного натурального числа во всех системах счисления с основаниями 2, 3, ..., 10. П реж де всего определим тип данных для представления числа в любой системе счисления: const t = ... type Цифры = a r r a y [1 ... t\ of 0..9 К аж д ой цифре в новой системе счисления выделяется по одному элементу массива. Следовательно, константа t ограничивает количество цифр в самом большом числе. Если, например, мы имеем дело с двоичной системой счисления (записанные в ней числа имеют больше всего цифр), а *= 50, то в массив можно поместить числа до 250 — 1. Составим процедуру, которая будет представлять н ату­ ральное десятичное число п при помощи цифр заданной рричной системы счисления и проверять, является ли оно 115


палиндромом: procedure палиндром (п, (* число *) p'.integer; (* система счисления*) v a r ц:цифры; v a r палиндр: boolean); v a r i, k'.integer; begin (* запись числа n в р-ричной системе *) k : = t ; (* const t — верхняя граница массива ц *) repeat ц [k\: — п mod р; k: = k — 1; п: — п div р until /2 = 0 ; for i: = k dow nto 1 do Ц № =0; (* является ли р-ричное число палиндромом *) i : = t + 1; repeat i: = i — !; k: = k - \ - 1 until (ц [к]фц [/]) or (k ^ i ) ; палиндр: = ц [к] = ц [/] end Включим составленную процедуру в программу, нахо­ дящую палиндромы заданного натурального числа в систе­ мах счисления с основаниями 2, 3, ..., 10: p ro g r a m палиндромы (input, output); const / = 50; type цифры = a r r a y [1../J of 0..9; v a r ц: цифры; n, (* исходное данное *)

i, k: integer; p :2 ..1 0; (* система счисления*)

па линд р; boolean; p rocedu re палиндром (n, (* число *) p '.integer; (* система счисления*) v a r ц: цифры; v ar палинд р'.boolean);' (* процедура данной задачи *) begin

read (п); writeln (’П А Л И Н Д Р О М Ы Ч И С Л А ’, п: 1); for р : = 2 to 10 do 116


begin палиндром (n, p, ц, палиндр)] if палиндр then (* печать результатов *) begin /: = 1; while ц [t]= 0 do i '• — i- + 1; for k: = i to t do write (ц [k\: = 1); writeln ( [ , p: 1, ]) end end end. При исходном данном л = 100 ЭВМ н апечатала П А Л И Н Д Р О М Ы Ч И С Л А 100 10201[3] 202 [7] 121 [9] (В квадратных скобках указывается система счисления.)

46. Большое произведение П ерем н ож ая большие числа, можно быстро получить переполнение. Поэтому, для того чтобы напечатать произве­ дение, превышающее maxint, надо применить искусственные средства. Составим программу для печати произведения двух чисел, которое может превышать максимально допустимое целое число maxint (см. зад ач у 2): p ro g r a m произведение (input, output); • v a r л:, у '.integer-, (* исходные д анны е*) procedure умножение (а, (* множимое *) Ь, (* множитель *) s: integer (* частичное произве­ дение *)); begin (* умножение *) if b ф б then begin s : = s + a*(6 mod 10); умножение (a, b div 10, s div 10); write (s mod 10:1) end else if s ^ = 0 then write (s ) 117


end; begin (* произведение *) read (x, y); w rite (x, ’* , у: 1, ’ = if ( х < 0 ) ф у ( < 0 ) then (* если знаки неодинаковы, *) (* произведение отрицательно *) w rite ( — ); умножение (abs (х), abs (х), 0) end. Процедура умножение умножает число а на каждую ц иф ­ ру числа Ь, начиная с единиц. Последняя цифра полученного произведения, сложенная с последней цифрой имеющегося в памяти частичного произведения, печатается, а все прочие цифры запоминаю тся — передаются как параметры при ре­ курсивном обращении к процедуре умножение. В самом конце производится умножение на первую (левую) цифру чис­ л а Ь. На этом умножение заканчивается. Тогда печатается начало результата — накопившееся частичное произведение без последней цифры (s div 10), а после него при в о звр а­ щении из рекурсии — все остальные цифры произведения (s mod 10) в обратном порядке по сравнению с тем, как они вычислялись при входе в рекурсию. В соответствии с этой программой ЭВМ перемножила числа 123 456 789 и 123 456 789 и напечатала такой ре­ зультат: 123456789*123456789 =15241578750190521 Значения параметров процедуры умножение ограничива­ ют максимально возможное частичное произведение. П о ­ скольку на множитель (второй параметр) умножаются о т­ дельно взятые цифры, то он может находиться в интервале [0; m a x in t]. Убедитесь в этом! Д л я того чтобы мы могли вычислить промежуточный результат a*(b mod 10), з н а ­ чение а не долж но превыш ать maxint div 9, а оператор присваивания s : = s + a*(b mod 10) мы сможем выполнить в том случае, если значение а будет вдвое меньшим, т. е. если максимальное значение множимо­ го (первого парам етра) будет примерно в 20 раз меньшим чем maxint. © Составьте программу умножения числа, не превы­ шающего maxint div 20, на число, превышающее maxint.

П8


47. Большие числа Иногда результат операций, выраженный большим чис­ лом, необходимо не только напечатать, но и сохранить в памяти ЭВМ, с тем чтобы с ним можно было производить какие-то другие действия. М ожно сгруппировать цифры числа и представить эти группы в виде отдельных чисел, объединенных в запись или массив. Тогда операции с б оль­ шими числами можно выразить в виде последовательности операций с числовыми компонентами. П рощ е всего (хотя и неэкономно) для каждой цифры числа выделить отдельный элемент массива. Так и сделаем. Определим массив для представления больших натуральных чисел: const / = Ю 0 ; type большое — a r r a y [1../] of 0..9 К а ж д а я цифра большого числа представлена отдельным элементом массива. Это естественное, но неэкономное пред­ ставление (в один элемент массива уместилось бы и большее количество ц и ф р ) . При помощи такого массива можно представить число, не превышающее Ю100. Этот предел установлен не случайно. Ведь 10шо часто рассматривают как пример очень большого числа (иногда в популярной литературе его назы ваю т гугол). Составим процедуру для сложения двух чисел типа боль­ шое: procedure сложение (а, Ь: большое; v a r с ’.большое); v a r / : 1../; вуме: 0..1; s:0..19; (* сумма двух цифр -f- в уме *) begin вуме: = 0 ; for j : = t dow nto 1 do begin s : = a \ j \ + 6 1/| -\-вуме; с [/]: = s mod 10; вуме: = 5 div 10 end; if в у м е = 1 then writeln ( П Е Р Е П О Л Н Е Н И Е ’) end Составим программу, которая напечатает факториалы чисел от 1 до 50: p ro g ra m больфак (output); const « = 50; /= 1 0 0 ; 119


type большое = a r r a y [1../] of 0..9; v a r f '.большое-, (* f:in teg er *) i, k'A ..t; j : 1..n; d:integer-, (* частичное произведение*) begin (* больфак *) f[t}: = 1; (* f: = 1*) for i: = 1 to / — 1 do f[« ']:= 0 ; for j : = 1 to n do begin d: = 0 ; for / : = f dow nto 1 do ( * / : = f * / * ) begin d : = d + f [/]*/; / [г]: = d mod 10; d: = d div 10 end; w rite (j, ’! = ’); k : — l;

while f [^J = 0 do (*writeln (f)*) begin шгг7е ( ); k: = k + l end; for i: = k to t do w rite (f [г]: 1); writeln end end. Д ействия с большими числами поясняются в коммента­ риях. Там указы ваю тся такие операции, которые надо было бы выполнять, если бы число f не было большим. © В программах, оперирующих с очень большими числа­ ми, было бы удобно иметь набор процедур, выполняющих арифметические операции ( + , — , *, div, mod) и операции сравнения ( < , > , = , ф ) . Составьте набор таких процедур. Большие числа представьте при помощи типа большое. © © Составьте программу для нахождения такого числа п, факториал которого близок числу Ю100, т. е. такого, что л ! < Ю ' 100; ( « + 1 )!> ’ю 100. 120


48. Точность действительных чисел Д ействительные числа в вычислительной машине пред­ ставлены приближенно. Д л я того чтобы оценить точность вычисления, необходимо знать, сколько знаков (цифр) вы ­ делено для представления числа. В языке П аскаль, как и почти во всех прочих языках, нет констант (аналогичных константе m axint), которые бы непосредственно указывали точность действительных чисел. Поэтому спраш ивать у м а ­ шины, сколько цифр выделено для действительного числа, приходится окольными путями. Приведем программу, которая печатает число десятич­ ных знаков, выделяемых для представления действительного числа: p ro g ra m точность (output); const система— 10; (* десятичная система счисления*) а — 1.0; (* число а имеет один точный зн ак *) v a r p:rea l; (*1.0, 0.1, 0.01, 0.001, ...*) п: integer-, (*число знаков*) begin р ; = 1.0; п: = 0; repeat п : —п + 1 ; р : = р / система until а = а-\-р; write (п) end. Цикл заканчивается, когда 1 . 0 = 1.0...01. Это означает, что последняя цифра числа 1.0...01 исчезла. Значит, ЭВМ вы­ деляет для представления действительного числа п — 1 десятичных знаков. Выполнив эту программу, ЕС ЭВМ напечатала число 7, а БЭСМ-6 — число 6. Если мы хотим выразить точность при помощи числа з н а ­ ков в какой-либо другой системе счисления, следует изме­ нить константу система.

49. Точное частное Как разделить одно целое число на другое так, чтобы получить частное (действительное число), имеющее столько точных цифр после запятой, сколько мы хотим? Попытаемся составить программу решения этой задачи. 121


Операция деления действительных чисел (ее знак / ) в данном случае нас не устраивает, поскольку ее результат о казы вается приближенным. Хорошенько подумав, нетрудно найти выход: получив очередную цифру частного, стоящую после запятой, сразу печатаем ее. Цифры частного мы полу­ чаем тем ж е способом, что и при делении столбиком: о с т а ­ ток умножаем на 10, а затем делим на делитель. p ro g ra m деление (input, output); v a r а, (* делимое *) b, (* делитель *) п, (* число точных цифр после запятой *) г: integer; begin (* деление *) read (а, b, п); w rite (а, : ', b: 1, ’ = if (а<сО)ф(Ь < 0 ) then (* числа имеют разные зн аки *) write (’ — ’); а: = abs (a); b : = a b s ( b ) ; write (a div b: 1, ’.’); for !': = 1 to n do begin a: — (a m od b)* 10; write (a div b: 1) end end. При a = 22, b = l , c = 20 ЭВМ напечатала 2 2 :7 = 3.14285714285714285714.

50. Д еление с большой точностью Есть любители получать возможно более точные зн ач е­ ния различных математических величин. Например, в XVI в. житель города Д ел ьф т (Голландия) Л удольф ван Койлен вы ­ числил значение я с точностью вплоть до 35-го зн ака после запятой и за в е щ а л высечь это число на своем надгробье. К ак составить программу, которая получила бы много точных десятичных зн аков числа е? Будем опираться на т а ­ кую последовательность: e = 1+ T T + i r + i r + - + i r + -

Если бы мы использовали вещественный тип данных, программа была бы такой: 122


p ro g r a m натлог (output); const эпсилон = 0.00001; (* за д а н н а я точность*) v a r e:real; (* сумма ряда и з*) п: integer; (*л*) член: real; (* членов *) begin член: = 1.0; (* нулевой член *) е: = ГО; п: = 1; repeat е : = е + член; п : = п + 1; член: = член/п (*я-й член *) until член < эпсилон; w rite (е:7:Ь) end Эта программа вычисляет значение е, суммируя члены ряда до тех пор, пока они не станут меньше, чем константа эпсилон (const эпсилон = 0.00001). Чем меньше константа, тем больше членов ряда склады вается и тем точнее получает­ ся значение е. Однако эту константу можно уменьшать толь­ ко до определенного предела. В зависимости от типа ЭВМ константа может быть от 10~б до 10- 9 (см. зад ач у 48). П о э­ тому, для того чтобы найти число е с большой точностью, поступим так, как в случае больших чисел (см. зад ач у 47) — цифры числа е сохраним в массиве, тип которого мы опре­ делим так: const t = ...; type послед = a r r a y [O.i] of [0..9] Условимся так ж е, что место запятой фиксируется после первой цифры, т. е. дробная часть находящ ейся в массиве последовательности начинается с элемента, индекс которого равен 1. Итак, при наличии такого массива мы сможем выразить значение е с точностью до t десятичных знаков, т. е. э п с и л о н = 10"'. Составим программу для нахождения числа е с большой точностью. В ней мы будем использовать тип послед. С права в комментариях мы будем указывать, как действия данной программы соответствуют прежней программе натлог. П о­ скольку программа, которую мы составим, будет осущ еств­ лять действия с числами, представленными в виде массива, то сложение, деление, сравнение, печать и получение еди123


ницы запишем в виде отдельных процедур или функций: p ro g r a m точность (output)] co nst t — 20; type послед— a r r a y [O.i] of 0..9; v a r n: integer] член; e: послед; (*e:real*) procedure единица (var v : послед)] v a r i: 1 begin v [0]: = 1; for i : = 1 to t do v [/]: = 0 end; (*единица*) p r o c e d u r e .прибавление (var e\ послед] член: послед)] (* прибавляется новый член к имеющейся последовательности *) v a r вуме:0.А ] s:0..19 ; (* сумма двух цифр -{-вуме*) i:0..t] begin вуме: = 0 ; for i : — t do w nto 0 do begin s : = e [t] + член [i] -j- вуме; e [г]: — s mod 10; вум е'.— s div 10 end end; (* прибавление *) p rocedu re деление (var член:послед] n .’integer)] (* нахождение n-ro члена: (n — 1)-й член*) (* делим на номер члена п *) v ar остаток, i, чл: integer] begin остаток: = 0 ; for i: = 0 to t do begin ч л : — остаток* 10 + член [г]; член [г]: = чл div я; остаток: — чл mod п end end; (* деление *) function мал (член:послед):boolean] (* останется ли сумма последовательности неизменной 124


при прибавлении нового члена, т. член [г]= 0 при всех г '= 1, 2, ..., /*) var i: integer;

е.

является

ли

begin г: == — 1; repeat

i: = i + 1 until (член [Л=^=0) or (i = t);

м а л : = член [г] = О end; (*мал *•) procedure печать (е: послед);

(* печатается значение е *) var г: \ ..t; begin

write (’Б = ' , е [0J: 1, for i : — 1 to / do

write (e [г]: 1); writeln end; (* печать *) begin (* точность *) единица (член); (* ч лен: = 1.0 *) единица (е); (* е : = 1.0 *) п: = 1; repeat

прибавление (е, член); (* е: = е - \ - ч л е н *) п: = п - \ - 1; деление (член, п) (* член: = ч л е н / п *) until мал (член); (* член < эпсилон *) печать (е) (* w rite (е) *) end.

Выполнив эту программу, ЭВМ н апечатала Е = 2.71828182845904523526. © Составьте программу, которая получит число я с боль­ шой точностью. Используйте любую последовательность, вы ­ раж аю щ ую данное число (см. зад ач у 5).

51. Простые дроби Типа данных, предназначенного для простых дробей, не существует. Их можно определить при помощи записи: type д р о б ь = record

s: integer; (* числитель *) 125


v: I..maxint (* знаменатель *) end Чтобы выполнять действия с простыми дробями, необ­ ходим набор процедур, вы раж аю щ их эти действия. П осколь­ ку результат арифметических операций с простыми дробями часто в ы р аж ается сокращенной дробью, то прежде всего составим процедуру д ля выполнения данного действия: p rocedu re ynp (var t: дробь); (* сокращение дроби *) v a r d: integer; function под (x, у '.integer): integer; (* наибольший общий делитель *) begin if x — 0 then н о д : = у else п о д : = нод (у m od x, x) end; begin (* ynp *) d : — нод (a b s ( t . s ), t.v)', t.s: — t.s div d; t.v: = t . v div d end (*ynp*) Д л я сложения и умножения двух простых дробей опи­ шем такие процедуры: procedure сложение (а, b '.дробь; v a r с'.дробь); begin c . s : — a.s*b.v-\-b.s*a.v; c .v : = a .v* b .v; ynp {c) end; procedure умножение (a, b '.дробь; v ar с ’.дробь); begin c.s: — a.s*b.s; c . v : = a .v * b .v ; ynp (c) end © Составьте процедуры вычитания и деления двух простых дробей. © © Если числитель и знаменатель представляют со­ бой большие числа, то при выполнении процедур сложение и умножение может получиться переполнение, хотя числи тель и знаменатель результата — сокращенной дроби — не превышают максимально допустимого числа maxint. 126


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

52. Десятичные дроби Любое рациональное число (следовательно, и любую простую дробь) можно представить в виде конечной или периодической десятичной дроби. Период принято заклю чать в скобки. Например, мы пишем 0 ,(3 ), 1,3(18), 2,7(136). Составим программу, которая будет положительную простую дробь п ревращ ать в десятичную и печатать ее (если дробь окаж ется периодической, то период будет напечатан в с к о б к а х ). Эта з а д а ч а похожа на зад ач у о точном делении (см. з а ­ дачу 49). Только в данном случае необходимо предвари­ тельно выяснить, является ли дробь периодической, и если да, перед тем как производить деление, надо установить, где начало и конец периода, чтобы было ясно, где напечатать скобки и когда прекратить деление. Является ли п р авильная дробь (т. е. т а к а я дробь, у кото­ рой числитель меньше знаменателя) периодической, можно установить, умножив числитель на 10, а затем разделив на знаменатель. Затем полученный остаток снова ум н ож ает­ ся на 10 и делится на знаменатель. Этот процесс повторяет­ ся до тех пор, пока остаток не станет равным нулю (в этом случае очевидно, что дробь является конечной) или же какому-либо ранее полученному остатку (в этом случае дробь является периодической, так как с этого момента снова будут повторяться и цифры частного, и остатки). Равные остатки устанавливаю т начало и конец периода. Таким образом, надо запоминать не частные, а остатки. М аксимальный остаток показывает, каково может быть м а к ­ симальное количество различных (неравных нулю) остатков: он на единицу меньше, чем знаменатель дроби (ч аст н о е ). Значит, максимальное значение знаменателя устанавливает размер массива, необходимого для хранения остатков, и м а к ­ симальную длину периода. И наоборот, max — м акси м аль­ ное число десятичных знаков в десятичной дроби — и приня­ тый размер массива остатков ограничивают знаменатель дроби. Определим тип дроби и составим процедуру для установ­ ления н ач ал а и конца ее периода: const тах = ...\ («наибольш ий зн ам енател ь*) 127


type интервал = 0..т ах; дробь = record s '.integer; (* числитель *) v : \ ..max (* знаменатель *) end; procedure период ( t : дробь; v a r начало, конец'.интервал); v a r k : интервал; ост: a r r a y [интервал] of интервал; begin for k : = 0 to m a x do ост [k]: = 0 ; начало : = m a x ; конец: = 0 ; repeat конец ; = конец + 1; ост [/.s]: = конец; t.s: = /.s*10 mod t.v until ост [7.s]=£0; if t.s = 0 then (* конечная д робь*) кон ец : = конец — 1 else (* периодическая дробь *) начало: = ост [t.s] end В процедуре период есть ряд непривычных моментов. 1. У массива ост тип индексов и тип элементов совпадают. Вместо того чтобы нумеровать элементы массива посредством их номеров в последовательности и остатков и присваивать элементам значения остатков, мы можем с тем же успехом сделать наоборот: элементу, у которого значение индекса равно остатку, присваивать номер данного остатка в после­ довательности. Тогда намного проще проверить, имеется ли уже в массиве новый остаток. , 2. В цикле repeat конечная дробь неотличима от периоди­ ческой. Ф ормально конечные дроби рассматриваю тся как частный случай периодических: их период равен нулю. Б л а ­ годаря этому верхний предел массива ост оказывается больше, чем долж ен был бы быть. 3. Процедура период не дает прямого ответа на вопрос, является ли дробь конечной. Если по окончании работы процедуры получается конечная дробь, то значение п а р а ­ метра начало равно константе max. При наличии данной процедуры мы без труда составим и всю программу: p ro g ra m десятичная (input, output); 128


const m a x = 100; (* наибольший зн ам енател ь*) type интервал — 0..max; д р о б ь = record s'.integer-, (* числитель *) v : 1..max (* знаменатель *) end; v a r p, g, позиция:интервал; t :дробь; p rocedu re период ( t '.дробь; v a r начало, конец: интервал); v a r k : интервал; ост: a r r a y [интервал] of интервал; begin for k: — 0 to max do ост [k]: = 0 ; начало: = m a x ; конец: = 0 ; repeat кон ец : = конец + 1; ост [f.s.]: = конец; t . s : = t . s . * 10 mod t.v until ост |7.s]=^0; if t.s = 0 then (* конечная дробь *) кон ец : = конец — 1 else (* периодическая дробь *) начало: = ост [t.s] end; (* период *) begin (* десятичная *) read (t.s, t.v); write (t.s div t.v, ’.’); (* целая часть *) t . s : = t.s mod t. v; период (t, p, g); for позиция: = 1 to g do begin if позиция = p then write ((); write (t.s * 10 div t.v: 1); t . s : = t . s * 10 m od t.v end; if р ф т а х then write ( ) ) end. Эту программу можно без труда применить и к отр и ца­ тельным дробям. О Измените программу таким образом, чтобы в ней не было ограничений на знаменатель дроби: если мы узнали 100 первых десятичных знаков и не обнаружили периода, 5 В. А. Д агене

12е


после сотого десятичного зн ака следует напечатать много­ точие. © © Полным периодом назы вается период, состоящий из максимально возможного числа цифр, т. е. период дроби -j- является полным, если в нем b — 1 цифра. Например, полный период имеют дроби у - = 0,(142857); - | - = 0,(285714); -1-=0,(0588235294117647). Составьте программу, которая печатала бы все дроби вида -L-, где п £ [ 1; 100], имеющие полный период.

53. Старинные меры В настоящее время чаще всего используется метриче­ ская система мер. Она основана на десятичной системе счис­ ления (1 м = 1 0 дм, 1 д м = 1 0 см и т. д .). Старинные меры основаны на различных системах счисления. Ещ е и теперь в некоторых местах используют дюймовую систему измерения: 12 дюймов составляют фут, 3 фута равны 1 ярду. Р а с с к а зы ­ вают, что эталон ярда был установлен в Англии в 1101 г. Это было расстояние от кончика носа короля Генриха I до кончика большого пальца его вытянутой руки. Определим тип данных, пригодный для описания длин посредством дюймовой системы измерения: type мера = record ярды: 0.. maxint\ футы: 0 .. 2 ; дюймы:0.Л1 end Составим функцию для замены дюймовых единиц длины на метрические. Мы знаем, что 1 дюйм « 2 , 5 4 см. function метр ( j : мера): real; begin метр: =(((/. ярды*3)-\- j. футы) * 12 + /. дюймы) * 0.0254 end 130


Составим процедуру для сложения двух измеренных в дюймовой системе измерения:

расстояний,

procedure сложение (а, о:мера; v a r s'.Mepa); v a r k:integer; begin

k: =а.дюймы-\- b. дюймы; s.дюймы: = k mod 12; k : = k div 12 -\~а.футы-\-Ь.футы; s.футы: = k mod 3 ;. s. я р д ы : = k div 3 4 - а.ярды-}-b.ярды end © Составьте процедуру, заменяю щую метрические еди­ ницы на единицы дюймовой системы измерения. . © © При использовании дюймовой системы площадь из­ меряют в квадратны х дюймах, квадратных футах и к в а д ­ ратных ярдах. Составьте процедуру для. сложения двух площадей, выраженных в дюймовой системе, и функцию для замены дюймовых единиц измерения площади на квадратные метры. © © © В старину жидкости и сыпучие тела измерялись в мерах (например, мера овса). В одной мере умещалось 6 гарнцев, а в гарнце — 4 кварты. Старинный гарнец, ис­ пользовавш ийся в Великом княж естве Литовском, был равен 5,6 л, а с 1765 г. был введен новый гарнец, равный 2,82 л. Точно т а к а я ж е система мер использовалась и у других народов, только различной о казы в ал ась величина единиц: старинный русский гарнец — 3,28 л, а польский гарнец р а ­ вен 3,77 л. Составьте процедуру, подходящую для того, чтобы пере­ вести количество жидкости из любого упомянутого вида единиц в любой другой. © © © © В Великобритании жидкости измеряют в г ал ­ лонах и бушелях. Один галлон равен 4,54609 л, 8 галлонов составляют бушель. Составьте процедуру для сложения двух объемов ж и д ­ кости, измеренных в английских единицах, и функцию для перевода количества жидкости, измеренного в галлонах и бушелях, в литры.

54, Действительные числа и бухгалтерия Пусть сберегательные банки по бессрочным вкладам вы ­ плачивают 2 % годовых от суммы в клада, присоединяе­ мых к вкладу. Если вкладчик не снимает денег с вклада, то проценты ежегодно начисляются со все большей суммы. 5*

.

131


Составим программу, которая будет вычислять, какой в е­ личины достигнет бессрочный первоначальный вклад через т лет, если вкладчик не будет ни снимать, ни вносить деньги: p ro g r a m проценты (input, output)-, v a r вклад \real\ m, (* после m лет *) i'.integer-, begin read (вклад, m); for i : == 1 to m do вклад: = вклад *1.02; write (вклад: 10:2) end. При исходных данных вклад = 100.0 и т = 1 ЭВМ, выпол­ няя программу, напечатала 101.99, т. е. первоначальный в клад в 100 р. через год увеличится на 1.99 р. Одна копейка «пропадает» из-за того, что операции с действительными числами ЭВ М производит приближенно. Чтобы получить возможно более точный результат, необходимо округлить число — изменить оператор печати так: w rite (вкл ад-\-0 .00 5:1 0:2) В этом случае ЭВМ напечатала бы правильный резуль­ тат. Абсолютно точный результат может быть получен только при использовании целых чисел. Поэтому в бухгалтерии лучше производить вычисления в копейках и только окон­ чательный результат печатать в рублях. Составим новую программу, в которой первоначальный вклад в ы раж ается в копейках: p ro g ra m проценты (input, output); v a r вклад, (* вклад, выраженный в копейках, *) т, (* после m лет *) i: integer; begin read (вклад, m); for i': = l to m do в к л а д : = (вклад * 102 -|- 50) div 100; write (вклад div 100,

РУБ., вклад mod 100:3,

КОП. )

end. Выполнив эту программу при вклад = 10 000 (в копейках) и m = 1, ЭВМ н ап ечатала 102 РУБ. 0 КОП. Д л я интереса при­ ведем напечатанный Э В М результат, который показывает, какой величины стал бы бессрочный в кл ад в 100 р. через 132


100 лет: 724 РУБ. 38 КОП. © Составьте программу, которая бы напечатала т а б л и ­ цу, показывающую, на сколько процентов возрастет бес­ срочный (двухпроцентный) и срочный (трехпроцентный) вклады через m лет ( 1 ^ т ^ 2 0 ) . © © Составьте функцию, которая бы устанавливала, через сколько лет первоначальный вклад удвоится (станет больше примерно вдвое). П араметр функции — годовой процент.

55. Высота музыкального звука Высота звука в физике определяется частотой колебаний и и зм ер я ется’в герцах (Гц) — числом колебаний в секунду. В музыке звуки распределены по октавам. Самые употре­ бительные октавы: субконтроктава, контроктава, большая, малая, первая, вторая, третья, четвертая, пятая октавы. В каждой октаве имеются следующие основные-звуки: до, ре, ми, фа, соль, ля, си. В музыке соотношение частот з в у ­ ков имеет большее значение, нежели абсолю тная частота. Тот же самый звук более высокой октавы имеет частоту вдвое больше. Например? частота до второй октавы в 2 р аза больше, чем частота до первой октавы, и в 4 раза больше, чем частота до малой октавы. О ктава равномерно разделена на 12 интервалов (полу­ тонов), так что соотношение частот двух соседних звуков равно vK j2tt 1.05946. Интервал между звуками ми ~ фа и си — до (более высокой октавы) составляет полутон, а между прочими основными звуками — тон (т. е. д ва полу­ тон а). Только 7 звуков октавы (из 12 возможных) имеют н а з в а ­ ния. Прочие звуки обозначаются при помощи названия соседнего звука вместе со специальным знаком: диезом — при повышении тона или бемолем — при понижении тона. Определим тип данных, характеризующих высоту звука в музыке: type о кт ава = (субконтроктава, контроктава, большая, м а­ лая, первая, вторая, третья, четвертая, пятая); з в у к = ( д о , ре, ми, фа, соль, ля, си); прибавка = (диез, бемоль, ничего); высота = record окт:октав а; з'.звук; п :п рибавка end


Принято, чтобы частота звука ля первой октавы была равна 440 Гц. Составим функцию для нахождения высоты любого тона: function частота (в: высота): real\ ' const полутон= 1.05946; v a r ок:октава; з в ’.звук; ч : rea l; (* частота *) begin ч: = 4 4 0 ,0 ; for о к : = р г е й (первая) dow nto в.окт. do ч: = ч / 2; for ок: = s u c c (первая) to в.окт do ч: = ч * 2; if в . з . = с и then ч: = ч * полутон * полутон else for з в : ==pred (ля) dow nto в.з do if зв = ми then ч: — ч/полутон else ч: =ч/полутон/полутон; if в . п = д и е з then ч : = ч * полутон else if в.п — бемоль then ч: = ч/полутон; частота: = ч end С начала принимается, что частота ч = 440 Гц. Затем от стандартной точки «опоры» — звука ля первой октавы — передвигаемся вниз или вверх на октаву 'и делим или умно­ жаем на 2. Затем передвигаемся на один тон вверх (после звука ля идет только один звук си) или вниз на требуемое число тонов. © Составьте функцию для нахождения интервала между двумя музыкальными звуками, выраженного числом полу­ тонов. Если первый звук ниже, чем второй, результат должен быть отрицательным. 0 © Составьте процедуру транспонирования звуков, которая зам ен ял а бы тон указанного музыкального звука, «передвигая» его на указанное число полутонов п вверх (если п > 0) или вниз (если п С О ) .

56. Случайные числа Мы не можем сразу сказать, какая сторона монеты или игральной кости окаж ется сверху, какую карту мы вытащим из колоды. Говорят, что результат такого эксперимента явля134


ется случайным. П овторяя эксперимент много раз, получаем последовательность случайных значений. Интересно, что не­ возможно предвидеть значение нового члена ряда — он не зависит от предшествующих членов. В зад ач ах , в которых моделируются жизненные ситуа­ ции (например, игры, эпидемии заболеваний и т. д .), тр е­ буются случайные значения. Значение любого числового типа можно получить из целых чисел, поэтому достаточно было бы иметь только генератор случайных целых чисел, т. е. такую функцию или процедуру, о б р ащ а ясь к которой мы каждый раз получали бы все новые и новые члены последо­ вательности случайных чисел (принадлеж ащ ие у казан но­ му и н те р ва л у ). Случайные числа можно получать различными методами, наиболее распространенный из них — рекуррентный, суть ко­ торого заклю чается в следующем: каж дое случайное число г к находится из предыдущего числа гк~ \ по формуле гк = = ( г к ~ I * сомножитель -{-прибавка) mod делитель. В этой формуле в качестве г0 выбирается любое число. Числа г 1, г 2, ..., Гк, получаемые по этой формуле, строго говоря, не будут случайными числами в теоретико-математическом смысле, т а к как Гк-е число зависит от значений гк- 1, г к_2, •••, г0. Поэтому числа, получаемые таким способом, принято назы вать псевдослучайными [4]. В соответствии с упомянутой формулой получаются числа на отрезке [0; делитель— 1]. Когда в ряду будет делитель чисел, ряд повторится заново. Какой величины будет повторяемая часть ряда, зависит от констант сомножи­ тель и прибавка. Чтобы таких повторений было меньше, т. е. получалась достаточно длинная последовательность сл у­ чайных чисел, необходимо надлеж ащ им образом подобрать упомянутые константы. В книге [4] предложены такие кон­ станты: делитель = 2 |б = 65 536; сомножитель = 25 173; прибавка = 13 849. Составим функцию для получения псевдослучайного н а ­ турального числа на отрезке [1; 65536): v a r 4Aen:integer; function nceedo:integer; .• /■n + .p , . (* псевдослучайные натуральные числа *) const сомножитель— 2Ъ173; , ... п ри б авка = 13849; делитель = 65536;.

У■

■ 135


begin член: — (член* сомножитель + прибавка)тоАделитель + 1; п сев до : — ч л е н -\-1; end Эта функция вычисляет 65 536 случайных чисел — после этого расположение чисел повторится. Обратим внимание на то, что функция псевдо использует глобальную перемен­ ную член. Если бы мы объявили ее параметром функции, то она д о лж н а была бы быть переменным параметром (поскольку каждый раз эта функция д о л ж н а передавать различные значения, а это было бы нежелательным по­ бочным эффектом функции). При первом обращении к функ­ ции переменной член следует приписать какое-нибудь исход­ ное значение. Часто бывает нужно получать псевдослучайные числа в каком-либо интервале. Д л я этого строится отображение результатов функции в тот интервал, в какой необходимо. Например, бросание игральной кости моделируется таким оператором: оч ки : = псевдо m od 6 + 1 Д л я того чтобы получить псевдослучайные действитель­ ные числа, удобнее всего порождать их из интервала [0; 1), видоизменив саму функцию порождения псевдо­ случайных чисел: v a r 4AeH\lnteger\ function п с е в д о :real\ (* псевдослучайные действительные *) (* числа в интервале [0;1)*) const сомножитель = 25173; п р и б а в к а = 13849; делитель— 65536; begin член: — (член * сомножитель-{-прибавка) mod делитель; псевдо: — член/делитель end Чтобы получать псевдослучайные действительные числа в большем интервале, надо умножить значение данной функ­ ции на соответствующую константу.

57. Вычисление значения л путем бросания иглы Мы опишем интересный и неожиданный способ вычисле­ ния числа л. Берется короткая швейная игла (лучше с обломанным о стр и ем )/и м ею щ ая на всем протяжении одина 136


ковую толщину. На листке бумаги чертится несколько тон­ ких параллельных прямых линий так, чтобы расстояние между соседними прямыми было вдвое большим, нежели длина иглы. После этого с одной и той ж е высоты бросают иглу на бу­ магу и смотрят, пересекает ли она какую-либо из проведен­ ных прямых или нет (рис. 14). При этом пересечением счи­ тается и тот случай, когда игла касается прямой лиш ь кон­ цом. Чтобы игла не о тскаки вала, под лист бумаги можно положить лоскуток ткани, поролон или что-либо подоб­ ное. Иглу бросают много раз, например сто или тысячу, и к а ж ­ дый раз отмечают, пересекла ли она прямую. Разделив число бросков иглы на число пересечений, получим приблизи­ тельное значение я: ^ __число бросков число пересечений

Чем больше число бросков, тем более точным оказывается полученное значение я. • Этот эксперимент осуществил в 1733 г. французский естествоиспытатель Ж о р ж де Бюффон (de Buff on, 1707— 1788). Интересно, какое значение я мы бы получили, если бы иглу «бросала» ЭВМ. Составим программу бросания иглы. Д л я того чтобы определить положение иглы на плоскости, достаточно знать координаты (сх, су) какой-либо точки иглы и угол а, который игла составляет с осью абсцисс (рис. 15). Условимся, что начерченные прямые параллельны оси абсцисс, а угол а острый. Д л я получения случайных чисел воспользуемся состав­ ленной в зад ач е 56 функцией псевдо, значения которой представляют собой действительные числа на промежутке [0; 1) и при этом точность всех значений одинакова. Иначе 137


обстоит дело, когда иглу бросают рукой на лист бумаги. Вероятность того, что игла упадет на край листа, много меньше, чем вероятность падения на середину листа. Так что в этом случае мы не можем применить функцию псевдо. Но ведь нам безразлично, около какой прямой упадет игла. Важны не координаты центра иглы, а расстояние от ее центра до ближайш ей прямой. Обозначим это расстояние буквой с. Тогда положение иглы мы можем определить при помощи двух случайных чисел с н а . Д лину иглы при­ мем равной 1 см, тогда расстояние от ее центра до б л и ж а й ­ шей прямой представляет собой число, находящееся в промежутке [0; 1). (Если центр находится на одинаковом удалении от обеих прямых, выберем нижнюю прямую.) Ф а к ­ тически мы ограничиваемся одной прямой, совпадающей с осью абсцисс, и считаем, что игла п адает недалеко от нее, т. е. ординату центра иглы мы можем случайным образом выбирать из промеж утка [ — 1; 1). В программе положение центра иглы мы будем определять так: с: = псевдо *2 .0— 1.0 Теперь необходимо получить случайный угол а. На к в а д р а т ­ ном поле обозначим две точки (х\, у 1) и (х2, г/2) и найдем угол, который прямая, соединяю щая эти две точки, образует с осью абсцисс. Поскольку размеры поля не играют роли, то мы можем считать его высоту и ширину равными 1 см. Имея случайные числа x l , у 1, х 2 , г/2, мы следующим образом опре­ деляем угол а и его синус: if x l = x 2 then s': = 1.0 (* игла перпендикулярна пря­ мым *) else begin альфа: = a r c t a n ((у2 — y l)/(x 2 — xl)) s : = s i n (альфа) end Теперь мы у ж е в состоянии проверить, пересекает ли игла прямую. Д л я этого необходимо найти ординаты кон­ цов иглы и проверить, находится ли прямая, совп адаю щ ая с осью абсцисс, между концами иглы. Д л я такой проверки составлена функция пересек. Приведем окончательный вид программы: p ro g ra m игла (output); const чброс = 10000; (* число бросков*) 7 v a r k, (* число пересечений *) член, (* глобальная переменная функции псевдо *) 138


m : integer; с, x \ , у 1, x2, y2, s:rea l\ function п с е в д о :real\ (* псевдослучайные действительные *) (* числа в интервале [0; 1)*) ... (см. зад ач у 56) function пересеч (конецХ, конец2:real):boolean-, begin пересеч: = (конец\ ^ 0 . 0 ) an d (0 .0 ^ к о н е ц 2 ) or ( к о н е ц 2 ^ 0 .0 ) and ( 0 . 0 ^ конец 1) end; (* пересеч *) begin (* длина иглы 1 см, меж ду прямыми 2 см; *) (* предположим, что игла упала так, что *) (* б л и ж а й ш ая п рям ая до ее центра у — 0.0; *) (* игла может пересечь только эту прямую *) член; = 5 0 0 0 ; k: = 0 ; for m : = 1 to чброс do begin с : — псевдо*2 — 1; (* случайным образом берем центр*) (* рядом с прямой у = 0.0*) (* случайным образом берем *) х \ : =псевдо\ (* две точки: *) у \ : = псевдо-, ( * ( х \ , у \ ) и *) х2 : = псевдо; (* (х2, у2) *) у2: = псевдо-, if х\ = х 2 then s : = 1.0 else s: = s i n (arctan ((у 1 — y2)/(x \ — -^2))); if пересеч (c -\-s /2 , c — s /2 ) then k : = k - \ - l end; write (чброс/ k : 12:10) end. 0 Ж . де Бюффон сформулировал и решил такую задачу: найдите вероятность того, что игла пересечет проведенные прямые, если лист бумаги разлинован параллельными прямы­ ми не только в горизонтальном, но и в вертикальном н ап р ав ­ лении (расстояния между всеми прямыми одинаковы). Вероятность вычисляется по формуле __число пересечений число бросков

Составьте программу для нахождения этой вероятности. 139


58. Площадь фигуры Покажем , каким образом можно приближений нм'нн'лить площ адь фигуры, ограниченной какой либо к|>пn<>ii, методом Монте-Карло. Название метода связано с городом МонтеКарло, славящ и м ся на весь мир своими игорными домами. В Основании этого метода л е ж а т случайные числа. Он особенно пригоден для тех случаев, когда уравнение кривой оказы вается сложным, трудноинтегрируемым. Кратко поясним его сущность. Исследуемую фигуру впи­ сывают в какую-либо другую фигуру, площ адь которой вычислить легко (например, в квадрат или в прямоуголь­ ник). Выбираются случайные точки (т. е. порождаются слу­ чайные числа, определяющие координаты точек), находя­ щиеся в пределах фигуры известной площади, и вычисляет­ ся, сколько точек попало внутрь исследуемой фигуры и сколько о казалось снаружи. Соотношение числа точек, о к а­ завш ихся внутри фигуры, к общему числу имеющихся точек будет пропорционально соотношению площадей фигур. Чем больше точек мы исследуем, тем более точной окаж ется д а н ­ ная пропорция. Из нее нетрудно вычислить площ адь иссле­ дуемой фигуры. Д л я того чтобы проил­ люстрировать данный ме­ тод, составим программу, которая будет вычислять площадь фигуры, ограни­ ченной синусоидой (т. е. кривой у = sin х при Рис. 16. Вычисление площади фигуры, О ^ х ^ л ) и линией у = 0. ограниченной половиной синусоиды. Д анную фигуру впишем в прямоугольник (рис. 16). p ro g ra m монтекарло (output); const г ч = 1 0 0 0 ; (* число пробных точек*) ли = 3.141592636; v a r х, у , (* координаты пробных точек *) площпр, (* площ адь прямоугольника *) площф: real; (* площ адь фигуры *) п, (* число точек, находящихся внутри фигуры *) член, (* глобальная переменная функ­ ции псевдо *) i: integer; function псевдо: real; (* псевдослучайные,действительные *) (* числа в интервале [0; 1) *) |4п ... (см. зад ач у 56)


begin член: = 2 0 0 0 ; n: = 0; for i: — 1 to тч do begin x: = n ceed o * n u ; (*0s^.x<.nu *) у: = псевдо; (* O ^ y c 1*) if у < sin (x) then n:. = n - j - 1 end; площпр: — nu\ (* п лощ адь прямоугольника *) площф: =(п/тч)* площпр; • • ш п /е (п лощ ф :4 : 2 ) end. Выполнив данную программу, ЭВМ напечатала 1.98. П лощ адь такой фигуры, вычисленная при помощи инте­ гралов, равняется 2: Л

\ sin х d x = — cos х л = 2. о 10 © Аналогичным образом мы можем применить метод Монте-Карло и для нахождения объемов фигур. Составьте программу для нахождения объема ш ара х2 + у2-j-z2 = 1 методом Монте-Карло.

59. Даты Едва ли не ежедневно мы пользуемся календарями и д а ж е не замечаем, что это, вообще говоря, непростая вещь. Человечество потратило тысячелетия на то, чтобы создать такой календарь, какой мы сейчас имеем. Приведем не­ сколько интереснейших фактов. Создать календарь — это значит соединить в красивую, удобную, правильную комбинацию три астрономических пе­ риода: сутки, лунный месяц и солнечный год. Но сделать это не так просто: все три числа, как нарочно, не ж елаю т соче­ таться друг с другом — год не делится ни на месяцы, ни на сутки. Попытаемся уяснить себе такие простые понятия, как год, месяц, сутки. Год (тропический) -— это промежуток от одного момента, когда центр Солнца проходит точку весеннего равноден­ ствия, до следующего такого момента. Этот промежуток длится 365 суток 5 часов 48 минут и почти 46 секунд, т. е. примерно 365,2422 суток. 141


Месяцем считается так называемый синодический месяц, или месяц лунных фаз, т. е. время от одного соединения Луны с Солнцем (новолуния) до другого. Месяц длится 29 суток 12 часов 44 минуты и почти 3 секунды, или около 29,53059 суток. Солнечные сутки д лятся 24 часа. Эти периоды представляют собой основание календаря. Таким образом, как мы видим, вовсе немыслимо связать воедино все три периода: сутки, месяц и г о д ..- в ка ле н д а­ ре, удовлетворяющем всем требованиям. Мы пользуемся солнечным календарем, созданным рим­ лянами. Всем известен календарь Юлия Ц е зар я, или, как принято говорить, юлианский календарь (созданный в 46 г. до н. э.). В нем год состоит из 12 месяцев, 365 дней. Каждый четвертый год — високосный — продолжается 366 дней. Однако в юлианском календаре год был на 11 минут 14 секунд более долгим, нежели тропический год, из-за чего весеннее равноденствие к XVI в. сместилось с 21 марта на 11 марта. Поэтому потребовалось усовершенствовать кален ­ дарь: отбросить излишек дней так, чтобы в будущем т а к а я ошибка не повторилась. В 1582 г. папа Григорий XIII пове­ лел 5 октября считать 15 октября 1582 г. (т. е. отбросить и з­ лиш ек в десять дней) и последний год любого столетия счи­ тать високосным (366 дней) только в том случае, если его номер делится на 400 (например, 1600, 2000). Юлианский год за четыре столетия забегал вперед на три дня. Чтобы уд ер ж ать его, астрономы Григория и отказались от трех високосных лет в четыре столетия. Григорианский год лишь на 26 секунд дольше, нежели тропический, и поэтому ошибка в 1 день набегает только за 3280 лет. В этом нет ничего страшного. Вскоре григорианским календарем стали пользоваться и в Литве. Однако с 1800 г. в Л итве (за исключением Занеманского края) снова стал использоваться старый календарь. Вторично в Л итве григорианский календарь был введен 15 ноября 1915 г.— этот день стали считать 28' ноября. Григорианским календарем мы пользуемся и в настоящее время. Теперь составим несколько функций, которые нам будут часто нужны для программ других зад ач с датами. П режде всего определим типы год, месяц, день: type

142

г о д = 1583.. 5000; месяц = 1 12; д е н ь = 1.. 31;


Эти типы мы будем использовать для решения других задач, связанных с датами. Мы будем пользоваться гри­ горианским календарем, поэтому годы мы определили н ачи­ ная с 1583-го (верхняя граница отрезка — 5000-й год; наде­ емся, что этого будет достаточно...). 1. О пределение високосных лет

Это очень простая функция, поэтому приведем ее без пояснений: function високос ( г д : г о д ) :Ьоо1еап\ (* является ли год високосным *) begin високос: = (гд mod 400 = 0) or (гд mod 100 0) and (гд m od 4 = 0) end Эта функция подходит только для григорианского кален ­ д ар я (последний год столетия считается високосным, только если его номер делится на 400). 2. О пределение числа дней в месяце

Это тож е достаточно несложная функция: function дмес (гд'.год; м ц:м есяц):integer: (* сколько дней в месяце мц года гд *) begin if (мц = 4) or (мц = 6) or (мц = 9) or (мц = 11) then дмес: = 3 0 else if мц = 2 then if високос (гд) then дмес: = 2 9 else дмес: = 2 8 else дмес: = 31 end В этой функции мы используем функцию определения ви­ сокосных лет високос. Иногда удобно назы вать месяцы не посредством чисел 1, 2, ..., 12, а при помощи слов ян варь , ф евраль, ..., декабрь. Д л я этого потребуется вышеприведенный тип месяц опре­ делить как скалярный тип: type

м е с = (январь, февраль, март, апрель, май, июнь, июль, август, сентябрь, октябрь, ноябрь, декабрь)

Перепишем функцию дмес для случая, когда месяцы опре­ делены по скалярному типу: 143


function дмц (гд :го д ; мц:мес) '.integer; (* сколько дней в месяце мц года гд *) (* месяцы определены по скалярному типу *) begin case мц of январь, март, май, июль, август, октябрь, декабрь'.дмц: = 3 1 ; апрель, июнь, сентябрь, ноябрь:дмц'. = 3 0 ; февраль :if високос (гд ) then дмц: = 2 9 else д м ц : = 28 end end Почти во всех зад ач ах мы будем использовать для об о зн а­ чения месяцев тип месяц, определяемый через, числовой отрезок, а тем самым и функцию дмес. Если где-либо нам понадобится определить месяцы по скалярному типу (напри­ мер, для того, чтобы непосредственно выводить на печать названия месяцев — см. зад ач у 67), то мы применим ф унк­ цию дмц и в этом случае подчеркнем, что тип мес является скалярным. 3. Проверка правильности обозначения даты Здесь и далее мы будем применять определение даты как запись следующего типа; type дата = record гд :го д ; м ц : месяц; дн'.день end Значение типа день определено отрезком 1..31. Но не в каждом месяце есть 29, 30 или 31 день. Поэтому значением записи может быть и несуществующая д ата, например 30.02.1986. Составим функцию, которая будет проверять, является ли данное обозначение даты правильным: function правдата (дт:дата)'.boolean; begin правдата: = д н . д н ^ д м е с (дт.гд, дт.мц) end В дальнейшем, составляя программы, в которых исход­ ные данные — даты, мы будем считать, что они обозначены правильно и не будем заниматься такой проверкой. © Составьте функцию д ля определения високосных лет по юлианскому календарю. 144


0 0 Составьте процедуру д ля печати заданной даты (значения типа данного дата), например 04.03.1986.

60. Д ата следующего дня Составим процедуру для установления даты з а в т р а ш ­ него дня, т. е. такую процедуру, результатом которой будет д ата, следую щ ая за датой данного дня: procedure завт ( сегодня :дата\ v a r завтра:дата)-, begin if с е го д н я .д н ф д м ес (сегодня.гд, сегодня.мц) then begin (* не последний день месяца *) завтра, гд: = сегодня.гд-, завтра.мц: = сегодня. мц; завтра.дн: = сегодня. дн-\- 1 end else if се го дн я.м ц ф 12 then begin (* не декабрь *) завтра.гд: = сегодня.гд; завтра.мц: = сегодня.мц -f-1; завтра.дн: = 1 end else begin (* 31 декабря *) завтра.гд: = сегодня. г д + 1; завтра.мц: = 1; завтра.дн: = 1 end end Д а т а определяется как тип записи (см. зад ачу 59), а функция дмес устанавливает, сколько дней' содержится в месяце (такж е см. зад ачу 59). Если бы мы передали исходное данное (сегодняшнюю дату) и результат (завтрашню ю дату) при помощи одного и того ж е п арам етра, мы получили бы несколько более кр а т­ кую процедуру: procedure завтдень (var дт:дата)\ begin if дт .днф дмес (дт.гд, дт.мц) then дт.дн: = д т .д н -(- 1 else if дт.мц Ф 12 then begin дт.мц: = дт.мц-\-1; 145


else

дт.дн: = 1 end begin дт.гд: = дт.гд- \ - 1; дт.мц: = 1; дт.дн: = \ end

end Это очень простая процедура. Она может быть исполь­ зована в электронных часах для установления даты. 0 Составьте процедуру для установления даты вчераш ­ него дня.

61. Будущая дата Нетрудно выяснить дату, которая наступит через несколь­ ко дней или д а ж е через несколько десятков дней. Однако, ес­ ли число дней велико, вычисления окаж у тся утомительными. Составим процедуру, которая будет определять дату, наступающую после того, как пройдет дн дней после у к а з а н ­ ной даты. Чтобы продвинуться вперед на один день, можно исполь­ зовать процедуру завтдень (см. зад ач у 60). Само собой разумеется, что продвигаться по одному дню — это слишком мало, если число дней дн велико. Поэтому рациональнее сначала передвигаться шагами величиной в год, затем — величиной в месяц, а далее — величиной в день. procedure будущ ее (д н :integer-, v a r дт:дата); (* нахождение даты через дн ( д н > 0) дней *) v a r Mu,:integer: function дгод (dz:zod):integer; (* число дней в году *) begin if високос (гд) then д г о д : = 366 else дгод: = 3 6 5 end begin (* возврат в н ачало года дт. гд *) for мц: = 1 to дт.мц — 1 do дн: = д н - \ - д м е с (дт.гд, мц); дн: = д н - \ - дт.дн; (* вперед на дн дней *)

146

while д н > д г о д (дт.гд) do (* находится год*) (* новой даты *) '


begin дн: = д н — дгод (дт.гд); дт.гд: = д т.гд- \ - 1 end; дт.мц: = 1; while д н > д м е с (дт.гд, дт.мц) do (* находится месяц *) (♦•новой даты *) begin дн: = д н — дмес (дт.гд, дт.мц); дт.мц: = дт.мц + 1 end; д т .д н : = д н end Д л я того чтобы шаги были равны целым годам и целым месяцам, в начале процедуры у казан н ая д ата «отодвигается» назад к началу года, а указанное число дней дн соответствен­ но увеличивается. В процедуре будущ ее используются функции из предыду­ щих задач (см. зад ач у 59) : високос (гд ) — является ли год гд високосным — и дмес (гд, мц) — сколько дней в месяце мц года гд. Д а т а определяется по типу записи (такж е см. з а ­ дачу 59). © Составьте процедуру для нахождения даты в про­ шлом, т. е. даты, которая была за дн дней до указанной даты.

62. Число дней между датами Составим функцию, которая будет находить, сколько дней прошло от одной даты до другой. Условимся, что во всех случаях вторая д ата является более поздней, нежели пер­ вая. Если обе даты совпадают, искомое число дней равно нулю. Например, от 5 октября 1990 г. до 11 октября того же года пройдет 6 дней, а от 1 декабря до 2 декабря одного и того же года — 1 день. Д аты определим по типу записи (см. зад ач у 59). Опять проще всего было бы применить для решения этой задачи процедуру завтдень (см. задачу 60) — вычислять дату с л е­ дующего дня до тех пор, пока она не станет равна второй (более поздней) из указанных дат. Однако если расстояние между двумя датам и велико (несколько лет), то т ак ая функция оказы вается очень неэффективной — к процедуре завтдень придется о б ращ аться много раз! Поищем более короткий путь. 147


д1

д2

4------ -— - 4 - — — гИ д ,

гд+1

-------------

|

гд+2------------------ гд+п

д Рис. 17. Схема нахож ден ия числа дней от даты д\ до даты д2.

Как можно было бы найти число дней между двумя датам и д \ и (32, представим графически (рис. 17). Из приведенной схемы видно, что искомый результат будет равен Д - д ' + д". Таким образом, необходимо вычислить три результата: 1) Д — число дней от н ачала д \ . г до н ачала д2.гд; 2) д' — число дней от н ачала года д \ . г д до даты <31; 3) д" — число дней от н ач ал а года д2.гд до даты д2. Эти результаты мы могли бы получить, имея такие две функции: функцию днг (<31, д2:дата) — для нахождения числа дней от н ачала года д \ . г д до н ач ал а года д2.гд (результат Д ) и функцию днд (дт:дата) — для нахождения числа дней от начала года дт. гд до указанной даты дд (о б р а ­ щ аясь к ней при парам етрах dl и <92, мы получим соответ­ ственно результаты д' и д"). function дни (dl, д2:дата):integer; (* число дней от даты (91 до д2 *) (* д ата д2 позже, чем <?1 *) function дни (<31, (32:(9ата):integer; (* число дней от начала года д \ . г д * ) (* до н ачала года д2.гд *) v a r дн, г д : integer; begin дн: = 0 ; for гд: = д \ . г д to д 2 . г д — \ do if високос (гд) then <3«:=(3н + 366 else дн: =(3н + 365; днч: = д н end; function днд (дт:дата):integer; (* число дней от начала *) (* года дт. гд до даты дт *) v a r дн, Mi{:integer; begin дн: = 0 ; for мц: = 1 to дт. мц — 1 do 148


дн: = д н - \ - д м е с (дт.гд,мц); д н д : = дн + дт. дн end; begin (* дни *) дни: = д н ч (<91, д2) — днд (д \)-\-дн д (д2)end Напомним еще раз, что в функции дни используются результаты функции високос (устанавливаю щей, является ли указанный год високосным) и функции дмес (находящ ей число дней в указанном месяце). Эти функции были разо'браны в зад ач е 59. Поэтому, чтобы использовать где-либо функцию дни, надо не забыть включить и упомянутые функции. Использование функции дни связано с одной трудностью: иногда бывает нелегко установить, к а к ая из д ат является более ранней, а к а к ая — более поздней (если, например, даты зад аю тся промежуточными результатам и). В этом случае следует составить функцию, проверяющую, которая из д ат является более поздней. Например, если первая д ата является более поздней, нежели вторая, то при обращении к функции дни надо только поменять параметры местами, чтобы более поздняя д ата стала второй. Приведем функцию позже, которая устанавливает, яв ­ ляется ли д ат а д2 более поздней, нежели <31: function позже (dl, д2: дата): boolean; (* позже ли д ата д2, чем д ата (51 *) begin позже: = ( д 2 . г д > д \ . г д ) ог (д2.гд = д \.гд ) and (( д 2 .м ц > д \.м ц ) ог (д2,мц— д\.м ц) and ( д 2 .д н > д \ . д н ) ) end © Исходные данные — д ата вашего рождения. С оставь­ те программу, которая наш ла бы, когда вам исполни­ лось (исполнится) 4000, 5000, 6000 и 7000 дней.

63. День недели Составьте функцию, которая устанавл и вал а бы, каким днем недели является у казан н ая д ата (д ата определена по типу записи — см. зад ач у 59). Д л я того чтобы решить эту задачу, надо знать день не­ дели какой-нибудь даты. Выберем какую-нибудь воскресную дату, например 07.01.1990 (можно было бы взять дату и для какого-нибудь другого дня недели, но вычисление ока зы в ае т­ 149


ся более естественным, когда в качестве точки отсчета вы ­ бирается воскресенье). Д л я того чтобы установить день недели, применим функ­ цию дни (см. зад ач у 62), которая находит число д н е й 'о т одной даты до другой. Поскольку мы не знаем, является ли у казан н ая д ата более поздней, нежели известная дата 07.01.1990, то нам придется прибегнуть к функции позже (см. зад ач у 62). Составим функцию днинед, результат которой будет о т­ носиться к типу днед.Он определяется следующим образом: type д н е д = (п н , вт, ср, чт, пт, сб, вс) Функция для определения дня недели указанной даты будет выглядеть так: function днинед (дт: дата): д н е д \ . (* день недели данной д а ты * ) v a r ди:дата\ (* выбирается д ата, для которой*) (* известен день недели — воскресенье *) /г:0.,6; begin (* выбирается д ата, для которой известен*) (* день недели — воскресенье: 30.12.1989 *) ди.гд: = 1989; ди.м ц : = 1 2; ди .д н : = 3 1 ; if позже (ди,дт) then k : = d n u (ди, дт) mod 7 else k : = ( 7 — дни (дт, ди) mod 7) mod 7 case k of 0 :днинеб: = в с ; 1 : дн ин ед; = пн; 2; днинед: = в т ; 3: днинед: = с р \ 4 : дн ин ед: = чт\ 5: днинед: =пт; 6: дн ин ед: = с б end end Чтобы использовать функцию днинед, следует включить в программу другие необходимые фунции: дни и позже (см. зад ач у 62), а кроме того, не забыть, что в функции дни используются функции високос и дмес (см. зад ач у 59). © Составьте программу, печатаю щ ую все годы нашего столетия, которые начинаются и заканчиваются в воскресенье. © © Составьте программу, которая н ап ечатала бы все годы нашего столетия, сод ерж ащ и е максимальное число воскресений. 150


64. День рождения Исходные данные — день вашего рождения. Мы составим программу, которая будет печатать все те годы и ваш возраст в эти годы, когда вы праздновали (будете праздно­ вать) день своего рождения в тот ж е день недели, когда вы родились. Мы составим такой календарь на период, пока вам не исполнится 100 лет. Самый простой способ составить программу для этой задачи — применить функцию днинед, устанавливающую день недели (см. зад ач у 63): д ля каж дого года проверяется, совпадает ли день недели дня рождения с тем днем, когда вы родились; если — да, производится печать. Однако можно составить и другой, более краткий в а ­ риант программы, в котором д а ж е не ищется, в какой день недели празднуется день рождения. Поскольку 365 делится на 7 с остатком 1 (соответствен­ но 366 m od 7 = 2 ), то в каждом следующем году дни недели продвигаются вперед на один (соответственно д в а ). Н а ­ пример, 07.11.1989 — вторник, 07.11.1990 — среда, 07.11.1991 — четверг, 07.11.1992 — суббота. Таким образом, вместо того чтобы д ля каждого года обращ аться к функции днинед (которая является не столь уж короткой), мы можем суммировать названные остатки: как только полученная сумма будет делиться на 7, мы по­ лучаем искомый результат. Приведем более эффективную программу деньрожд: p ro g ra m деньрожд (input, output)-, type год = 1583..5000; м е с я ц а 1..12; д е н ь = 1.31; дата = record гд :го д ; мц\месяц\ дн:день end; v a r р:дата\ (* д ат а рож ден и я*) возраст: 1.. 100; х : 0.. 1; z :in te g e r ; function високос (г д :го д ):in teger\ (* является ли год гд високосным *! ... (см. зад ач у 59) ч - : begin (* деньрожд*) read (р.дн, р.мц, р.гд);

....... ^

151


writeln ( ДАТА Р О Ж Д Е Н И Я : , р .д н : 2, . , р .м ц :2, . , р. гд: 4);

г: = 0 ; if р .м ц ^ .2 then х : = 1 else х : = 0 ; for возраст: = 1 to 100 do begin р.гд: = р . г д - \ - 1; if високос (р . г д — х) then z : = z + 2 else z: = z + 1; if (р . м ц = 2) and (р.дн= 2 9 ) and not високос(р.гд) then (* если день рождения 29 февраля, а *) (* год невисокосный, то дня рождения нет *) else if z mod 7 = 0 then writeln (р.гд, возраст :4) end end.

65. Сто лет семье Немногим суждено отметить столетний юбилей. З н а ч и ­ тельно б ольш ая вероятность того, ч'то сто лет исполнится семье. Надо только сложить возрасты всех членов семьи и дату, когда мы получим сто лет, объявить семейным юби­ леем. Сколько дней длится столетие? Столетие состояло бы из 36 500 дней, если бы не было високосных годов. В течение ста лет бывает 24 или 25 високосных лет (см. зад ач у 59). Поскольку столетий, в которых 24 високосных года, больше, то мы будем считать, что в столетии бывает 24 високосных года. Следовательно, сто лет у нас будут равны 36 524 дням. Число членов семьи может быть различно. Д аты их рождения можно было бы сохранить в массиве. При опреде­ лении массива пришлось бы как-то ограничить число членов семьи. Однако, немного подумав, мы можем не прибегать к массиву д ат рождения. Будем рассу ж д ать так. Допустим, ЭВМ прочитала лиш ь часть всех д ат рождения. Среди них можно найти Дм — дату рождения младшего члена семьи. Тогда мы посчитаем 5 — сумму возрастов всех членов семьи на данную дату. Н айдя ее, мы можем забы ть все даты р о ж ­ дения, за исключением Дм. Вводя новую дату рождения Дч, прежде всего мы должны проверить, является ли она более ранней, нежели Дм. Если — да, то надо найти разность д ат Д ч и Дм и прибавить ее к сумме S. Если — нет, то значит, что новый член семьи младше, чем все прежние. Тогда р а з ­ ность д ат Дм и Д ч надо умножить на число уже обрабо152


тайных д а т и произведение прибавить к сумме S, а дату Дм заменить на дату Дч. Если введены все даты, а сумма S меньше чем 36 524, можно найти дату будущего юбилея. Она наступит тогда, когда самому младшему члену семьи исполнится дн =

36 5 2 4 - ? -д н е й

п

Д л я того чтобы программа была проще, все эти действия можно начать с первой даты, считая ее датой рождения младшего члена семьи. Мы обсудили алгоритм достаточно исчерпывающе. Теперь осталось только написать программу: p ro g ram столетие [input, output); const сто = 36524; (* сто лет *) type го<9 = 1583..5000; м е с я ц = 1..12; день = 1..31; дата = record г д :год\ мц: месяц; д н :день end; var п, (* число членов семьи *) s'.integer; (* число дней, прож иты х*) (* всеми членами семьи *) (* до рождения младшего члена *) дм, (* д ата рождения младшего члена *) (* д ата рождения члена семьи *) дч, дюб'.дата; (* юбилей *) function високос (гд :г о д ) :boolean; (* является ли год гд високосным *) ... (см. зад ач у 59) function дмес (гд'.год; мц:м есяц):integer; (* сколько дней в месяце мц года гд *) ... (см. зад ач у 59) function дни (<91, <92:дата):integer; (* число дней от даты <91 до <92 *) (* д а т а <92 позже, чем <91 *) ... (см. зад ач у 62) function позже (<91, <92: дата): boolean; (* позже ли д ата <92, чем <91 *) ... (см. зад ач у 62) procedure будущ ее (д н : in teger; v a r дт'.дата); 153


(* нахождение даты через дн ( д н > 0) дней *) ... (см. зад ач у 61) begin (* столетие *) readln (дм.гд, дм.мц, дм.дн); п : = 1; s : = 0 ; while not eof do begin readln (дч.гд, дч.мц, дч.дн); if позже (дч, дт) then s : = s - \ - d n u (дч, дм) else begin s: = s-\-n*dnu (дм, дч); дм := дч end;

п : — п -f-1 end; дю б.гд: = д м .г д ; дюб.мц: = д м .м ц ; дюб.дн: = д м .д н ; if s > cto then writeln (’ЕСТЬ Ч Л Е Н С Е М ЬИ , Р О Д И В Ш И Й С Я ’ ’ У Ж Е П О С Л Е Ю Б И Л Е Я ’) else begin будущ ее ((сто — s) div п, дю б); write (’С Т О Л Е Т Н И Й Ю Б И Л Е Й ’); writeln (дюб.дн; 3, ’.’, дю б .м ц :2, дюб.гд: 4) end end. 0 Измените программу юбилей таким образом, чтобы найти и напечатать все юбилеи семьи, кратные с т а : 100, 200, 300-летние и т. д., которые будут иметь место начиная с даты рождения самого младшего члена семьи и до того момента, когда самому старшему члену семьи исполнится 100 лет. © © В этой зад ач е мы вычисляли, сколько в среднем дней содерж ится в одном столетии. М ожно было бы н а ­ метить юбилей и по-другому, например суммируя только полные годы, прожитые каж ды м членом семьи. Тогда все юбилеи семьи будут совпадать с днем рождения кого-то из членов семьи, а в отдельных случаях (например, среди членов семьи есть родившиеся в один и тот же день года) юбилеев может вообще не быть. 154


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

66. Биологические ритмы В жизни человека бывают творческие и бесплодные, счастливые и несчастливые дни, дни, когда он бывает в при­ поднятом или в подавленном настроении. Замечено, что в о з­ можности человеческого организма изменяются периоди­ чески. По прошествии определенного числа дней (периода) организм в озвр ащ ается в то же самое состояние. Часто био­ логические ритмы вычисляют, основываясь на гипотезе, что существуют три цикла: физический (его период равен 23 дням), эмоциональный (период — 28 дней) и интеллектуаль­ ный (период — 33 д ня). Кривые биологических ритмов могут быть представлены в виде синусоид (рис. 18). Н ачало всех трех кривых — день рождения. В первой половине каждого периода значения синусоиды положительны — это дни рабочего, приподнятого настроения.; в дни второй части периода (когда значения синусоиды отрицательны) человек находится в пассивном, плохом настроении. В самом начале (после дня рождения) все биологические ритмы попадают н отрицательную часть периода. В каждом периоде имеется четыре критических дня: пня р аза синусоида находится в нулевой точке и по одному разу получает максимальное и минимальное значения. В

Положительная сторона

сризическии эмоциональный интеллектуальный

Отрицательная сторона I’m-

18. Ц икл биологических ритмов.

155


критические дни состояние организма и психики человека неустойчиво, легкоранимо. Действительно ли циклы биологических ритмов являю тся точно такими и одинаково ли они подходят для каждого человека, пока еще окончательно не установлено. Биологи­ ческими ритмами стали особенно интересоваться в последние десятилетия, поскольку соответствующие календари очень легко получить при помощи ЭВМ. Составим программу, которая будет печатать календарь биологических ритмов на один год. Ее исходные данные — день рождения и год, д ля которого мы хотим получить биологические ритмы. Д л я краткости мы приведем программу, результаты кото­ рой печатаются расположенными достаточно простым о б р а ­ зом — для каждого дня каждого месяца указы вается состоя­ ние физического, эмоционального и интеллектуального циклов: « + » — полож ительная (хорош ая) половина цикла; « — » — отрицательная (плохая) половина цикла; « * » — | критический день. Это несложная, хотя и довольно длинная программа. Д л я того чтобы напечатать биологические циклы на один ме­ сяц, составлена процедура биомесяц. Если период цикла делится на д ва (таков эм оциональ­ ный цикл), то кривая пересекает ось абсцисс между двумя точками, помечающими д ва соседних дня. Поэтому мы вво­ дим два критических нулевых дня (null и пи12). А нало­ гичным образом и по той же причине мы ввели д ва м акси ­ мума (m axi и m ax2) и два минимума (min 1 и mm2). П р и ­ ведем законченную программу и часть результатов, н апеча­ танных ЭВМ (рис. 19): ДАТА РОЖДЕНИЯ: 18 12 1954 БИОРИТМЫ 1987 Г. . . ЯНВАРЬ ФИЗИЧЕСКИЙ*--- *++++**++++**--- **--- *++ ЭМОЦИОНАЛЬНЫЙ— *++++++*++++++*----- *----- * ИНТЕЛЛЕКТУАЛЬНЫЙ— : -- *------ *+++++++*+++++++** ФЕВРАЛЬ ФИЗИЧЕСКИЙ++#*++++**-- -**--- **+++**+ ЭВДИОНАЛЬНЫЙ++++++*++++++*--- --------* ИНТЕЛЛЕКТУАЛЬНЫЙ------ *------ *+++++++*++++ МАРТ

ФИЗИЧЕСКИЙ+++ **-- -**--- ■*++++**++++**--ЭМОЦИОНАЛЬНЫЙ++++++*++++++*- ------ *------- *+++ --- -*'+++++++#++ ИНТЕЛЛЕКТУАЛЬНЫЙ+++**--Рис. 19. Ч асть результатов, напечатанны х програм мой биоритмы. 156


p ro g ra m биоритмы (input, output)-, type го(3= 1583..5000; месяц = 1..12; <5емь = 1..31; дата — record гд'.год; мц: месяц; д н :день end; v a r р, (* д ата рождения *) био'.дата; физ, эм, инт:integer; (* счетчики периодов*) мц: месяц; д: integer; (* количество дней *) function високос (гд'.год)’.boolean; (* является ли год гд високосным *) ... (см. зад ач у 59) function дмес (гд'.год; м ц:месяц)'.integer; (* сколько дней в месяце мц года гд *) ... (см. зад ач у 59) function дни (51, д2: дата): integer; (* число дней от даты 51 до даты д2 *) ... (см. зад ач у 62) procedure биомесяц (var coc:integer; (* состояние в первый *) (* день месяца *) п е р '.integer; (* период — 23, 28 *) (* или 33 дня *) мц:месяц (* месяц *)); v ar max 1, m ax2 m ini, min2, n u ll, nul2, i: integer; begin (* биомесяц *) m a x i : — пер div 4; max2: = ( n e p - \- 2 ) div 4; m i n i : — nep — m axi; mi n 2 : = nep — max 2; n u l l : = n e p div 2; nul2: = ( n e p -\- 1) div 2; for г: = 1 to дмес (био.мц, мц) do begin if (coc = 0'l or \co c = n u ll) or (coc = nul2) (coc — m ax 1) or (coc = m a x2) or (coc = m inl) or (coc = min2) then (* критический день *) У 1 write ( * )

or

157


else if c o c < n u l l then (* полож ительная половина *) w rite (’. + eise (* отрицательная половина *) write coc: — {coc-\- i) mod nep end; writeln end; (* б uoмесяц *) begin (* биоритмы *) read (р.гд , р.мц, р.дн, био.гд); writeln ( ДАТА Р О Ж Д Е Н И Я : , р .д н :3, р.мц:3, р . г д : 5); writeln ( Б И О Р И Т М Ы , б и о .гд :5, Г.); био.мц: — 1; био.дн: = 1; д : = д н и {р, био); ф и з : = д mod 23; эм: = д mod 28; инт: = д m od 33; for мц: — 1 to 12 do begin w rite ln -,. (* пропускается строка *) case мц of , i : write ( Я Н В А Р Ь ); 2: write (’Ф Е В Р А Л Ь ’); 3 '.write (’М А Р Т ’); 4 : write (’А П Р Е Л Ь ’); 5: w rite (’М А Й ’); 6 : w rite (’И Ю Н Ь ’); 7 '.write (’И Ю Л Ь ’); 8: write (’АВГУСТ’); 9 : w rite (’С Е Н Т Я Б Р Ь ’); ГО: w rite (’О К Т Я Б Р Ь ’); 11: write (’Н О Я Б Р Ь ’); 12: write (’Д Е К А Б Р Ь ’) end; writeln-, write (’Ф И З И Ч Е С К И Й ’: 16); биомесяц {физ, 23, мц); w rite (’Э М О Ц И О Н А Л Ь Н Ы Й ’: 16); биомесяц {эм, 28, мц); write (’И Н Т Е Л Л Е К Т У А Л Ь Н Ы Й ’); биомесяц {инт, 33, мц) end end, 0 Если у каких-либо двух (или у всех трех) б ио гических ритмов совпадают критические дни, то такой день н азы вается д в а ж д ы (трижды) критическим. Дополните программу биологических ритмов таким о б р а ­ зом, чтобы она находила и п ечатала д в а ж д ы и трижды кри­ тические дни. 158


0 © Сторонники селенобиологической гипотезы (селенобиология исследует влияние Луны на земные организмы) утверждают, что периоды многодневных ритмов, зависящ ие от Луны, не долж ны были бы п редставлять собой точно определенные отрезки времени. По их мнению, Л ун а диктует некоторый ритм, который не является таким у ж регулярным. В соответствии с этой гипотезой продолжительность пе­ риодов такова: физический период — 23,688437 суток, эмоциональный период — 28,426124 суток и интеллектуаль­ ный период — 33,163812 суток. Составьте программу для печати селенобиологического календаря биологических ритмов, если исходные данные — день рождения лица, о котором идет речь, и год, для ко­ торого требуется найти биоритмы,— известны.

(»7. Настенный

кал ен д ар ь

Составим программу для печати календ ар я произволь­ ного года. Дни месяца мы расположим, как обычно, столбцами, I л к что каж ды й столбец соответствует одной неделе. М есяц может иметь самое большее 6 столбцов. Например: 2 3 4 5 6 7 8

9 10 11 12 13 14 15

16 17 18 19 20 21 22

23 24 25 26 27 28 29

Календарь одного месяца определим следующим образом: type месяц — a r r a y [1..6, пн..вс] of 0..31 11ри помощи нулей мы помечаем несуществующие дни, при печати вместо нулей мы оставим пробелы.

Теперь весь календарь мы можем определить при помо­ щи массива, который составляют двенадц ать массивов типа месяц: type календ — a r r a y [январь..декабрь] of месяц Итак, з а д а ч а программы — сформировать массив типа 1,и итд и напечатать его в соответствии с определенными

правилами. Поскольку для всех месяцев массивы форми­ руются сходным образом, составим процедуру form д ля обраишаиия массива одного, месяца, т. е. массива типа месяц. 159


Д л я того чтобы образовать массив месяца, необходимо знать, с какого дня недели начинается месяц и сколько в этом, месяце дней. Д л я нахождения числа дней в месяце мы имеем функцию дмц (см. зад ач у 59). День недели можно было бы установить с помощью функции днинед (см. зад а чу 63), однако она является длинной, а нам достаточно знать только, каким днем является первый день первого месяца (т. е. с какого дня недели начинается новый год) С каких дней недели начинаются прочие месяцы, мы будем устанавливать из непосредственно предшествующего месяца Д л я этого составим особую краткую функцию новый. А л ­ горитм, устанавливаю щ ий день недели нового года, сходен с алгоритмом, находящим день рождения (см. зад ач у 64), поэтому мы не будем объяснять его более подробно. М ассив мц типа месяц формируется следующим образом: д: =

день недели в первый день месяца

сг: = 1; (* начинаем с первого столбца for д н : = 1 to

количество дней в месяце

do

begin ми [ст, д ] : = д н \ д: = далее (д); (* следующий день недели *) if <3= пн then ст: — ст-\-1 end С начала заполним нулями пустые клетки в столбце ме­ сяца, т. е. элементы массива месяц. Подчеркнем, что нули могут быть только в первом, пятом или шестом столбце Календарь формируется посредством двенадцатикратного (каж ды й раз для нового месяца) обращ ения к процедуре form, алгоритм которой мы только что описали. При н али ­ чии сформированного календаря остается только красиво его напечатать. Д л я этого составим процедуру печать. М о ж ­ но расположить месяцы различным образом: на одной го ризонтали по 2, или по 3, или по 4, или по 6. В высоту календарь в этих случаях имел бы соответственно 6, 4, 3, 2 месяца. П л ощ адь календаря определим при помощи кон станты чсмц. Следовательно, для процедуры печать мы долж ны указать, начиная с какого и до какого месяца про изводить печать. Мы составим процедуру печать таким обра зом, чтобы она печатала строку высотой в один месяц. Приведем законченную программу: 160


p ro g ram календарь (input, output)-, const чсмц = 3; (* площадь календаря — *) (* число месяцев в строке *) type г о д = 1583..5000; мес = (январь, февраль, март, апрель, май, июнь, июль, август, сентябрь, октябрь, ноябрь, декабрь)-, днед = (пн, вт, ср, чт, пт, сб, вс); месяц = a r r a y [1..6, пн...вс\ of 0..31; календ = аггау [январь..декабрь] of месяц; var гд'.год; (* исходное дан ное*) кал:календ; от, до, мц'.мес; д'.днед; k: integer; function високос (гд :г о д ) :boolean; (* является ли год гд високосным '*) ... (см. зад ач у 59) function дмц ( г д :год; мц'.мес):integer; (* сколько дней в месяце мц года гд *) ... (см. зад ач у 59) (unction далее (s :днед):днед; (* день, идущий после дня недели, s *) begin if s = ec then далее: = п н else далее: = succ(s) end; function перед (s:dned):dHed; (* день, идущий перед днем недели, s *) begin ii s = пн then перед: = вс else перед: = p r e d ( s ) end; function новый(гд:год):днед; (* в какой день недели начался год г д * ) var д'.днед; г:го д; begin (* новый *) (* выбирается год, для которого известен *) ' день недели Нового года, например 1984 — воскресенье*) д: = в с ; if гд > 1984 then for г: = 1984 to г д — 1 do if високос(г) then д : = далее(далее(д)) else д: = д а л е е (д ) else И А Дпгене

161


for г: = 1983 dow nto гд do if високос(г) then д : = перед(перед(д)) else д: = neped(d); н овы й : = d end; (* новый *) procedure form(maxMec: integer; (* число дней в месяце*) v a r мц:месяц; v a r д:'днсд); (* формируется массив месяца мц *) v a r дн, ст: integer; х:днед; begin (* form *) for r : — пн to вс do b -g in мц [1, x] : = 0 ; мц [5, x]: = 0 ; мц [6, x\: = 0 end; c t : = 1; for дн: = 1 to тахмес do begin мц [ct, d] : = д н ; д := далее(д ); if д = пн then ct : = ct -\- 1 end end; (* form *) p rocedu re печать (от, до:мес; кал:календ); (* печатается одна строка высотой в один месяц *) v a r мц:мес; ст, д н : integer; д:днед; begin for мц: — от to до do w rite (м ц :20); writeln; (* пробел, после строки *) writeln; (* названий месяцев *) for д: — пн to вс do begin w rite (д:6); for м ц : = о т to до do begin w rite (’ ’: 2); for c t : — 1 to 6 do begin дн: = к а л [мц, ст, <3]; if д н ф 0 then write (д н :3) else write (' ’:3) 162


end end; writeln end; writeln (* пробел перед строкой названий месяцев'*) end; (* печать *) begin (* календарь *) readied)-, д: — новый(гд)-, for мц: = январь to декабрь do 1огт(дмес(гд, мц), кал [м ц ], д); writelnizd: чсмц*20 div 2); writeln; о т : = январь; repeat до: — о т ; for г: = 2 to чсмц do до: = succ(do); печать(от, до, кал); if д о ф д е к а б р ь then от: = s u c c (d o ) until до = декабрь

end. Календарь 1987 г., напечатанный ЭВМ, которая выпол­ нил;! данную программу, приведен на рисунке 20.

<»N. Лунный календарь Мы пользуемся солнечным календарем, основанием коп'рпю является цикл видимого движ ения Солнца по небу — и* I К алендарь составлен таким образом, что важные моН'иты движения Солнца — дни весеннего и осеннего равноичк’тиия, самый длинный и самый короткий день — по­ падают на один и тот же (или почти один и тот же) день ". 1. 1, Между тем роль лунного цикла оказы вается второсте..........и. Месяц длиннее, чем лунный цикл. Поэтому фазы Ь.пы не связаны с определенными днями месяца. И мусульманских странах используется лунный- кален| ' р|. Лунные годы считают начиная с 16 июля 622 г. по минскому календарю (дата бегства М агомета из Мекки в /цшу). Таким образом, в лунном календаре э та д а т а пред• I т л и е т собой первый день первого месяца первого года. I'.i к'ндарь составлен таким образом, что год всегда начинм-м-я с новолуния. Поскольку лунный период составляет “ ‘ Н)(5 суток, то месяцы лунного календ ар я (всего их 12) 163


1987

ЯНВАРЬ ПН ВТ

СР чт пт (Ж

3

ВС

4

1 2

5 12 19 6 13 20 7 14 21 8 15 22 9 16 23 10 17 24 11 18 25

ФЕВРАЛЬ 26 27 28

29 30 31 1

2 9 3 10 4 И 5 12 6 13 7 14 8 15

АПРЕЛЬ пн ВТ СР

чт пт СБ ВС

6 13 20 27 7 14 21 28

4 11 18 25 5 12 19 26

1 8 15 22 29 2 9 16 23 30 3 10 17 24 4 11 18 25 5 1 2 -1 9 26

6 13 20 27 7 14 21 28 1 8 15 22 29 2 9 16 23 30 3 10 17 24 31

гш чт пт СБ ВС

1 2 3 4 5

6 13 20 27 7 14 21 28 8 15 22 29 9 16 23 30 10 17 24 31 11 18 25 12 19 26

АВГУСТ

1 2

ОКТЯБРЬ

пн

5 12 19 26

6 13 20 27 7 14 21 28

СБ ВС

1 8 15 2 9 16 3 10 17 4 11 18

3 4 5 6 7 8 9

10 17 24

11 18 25 12 19 26 13 20 27

14 21 28 15 22 29 16 23 30

НОЯБРЬ

ВТ СР

чт пт

23 24 25 26 27 28

МАЙ

ИЮЛЬ

ВТ СР

16 17 18 19 20 21 22

22 29

23 30 24 31 25

Рис. 20. К ален д арь 1987 года.

1

2 3 4 5 6 7 8

9 16 23 10 17 24 И 18 25 ,12 19 26 13 20 27 14 21 28 15 22 29


'•одержат 29 или 30 дней. Нечетные месяцы содержат по 30 дней, а четные — по 29 дней. Год по этому календарю питает от подлинного лунного года на 0,0306x12 = 0,3672 суток. '.;i 30 лет эта разница составляет 11 суток. Поэтому в кажк)М 30-летнем цикле 11 лет считаются високосными. При­ нято считать високосными 2, 5, 7, 10, 13, 16, 18, 21, 24, 27 и 29-й год цикла. Дополнительный день прибавляется к последнему месяцу, который в високосные годы вместо ','9 дней содержит 30 дней. Обычный лунный год содержит 354 дня, високосный — Лунный год на 11 дней короче, нежели солнечный год. II" пому было бы интересно составить программу, которая >\ пт вычислять, например, какая сегодня дата по лунному календарю. ('оставим программу, которая будет превращать указан­ и ю дату григорианского календаря в соответствующую • и дату лунного календаря. Прежде всего приведем схему программы: |>io|k>ram лунный (input, output ); type го д — 1583..5000; (* григорианский календарь*) м есяц = 1.. 12; день = 1..31; д а т а = record г д :г о д ; м ц : м есяц; дн :д ен ь end; д атал ун — record (* дата лунного календаря*) г д : 1..5000; м ц : 1..12; дн : 1..30 end; var дд, (* заданная дата григорианского календаря *) д г :д а т а ; (* 01.01.1583 *) д л :д а т а л у н ; (* искомая дата лунного*) (* календаря *) г, d e n :in te g e r; (* число дней от начала *) (* лунного года до дд *) |miction дни (<31, d 2 :d a T a ):in te g e r; (* число дней от даты (31 до даты <32 *) ... (см. задачу 62) procedure м агом ет ( д '.integer; var д л :д атал ун ); 165


дата лунного календаря — д ( д > 0 ) дней спустя после бегства Магомета begin ищется число дней ден от 16.07.622 до 01.01.1583 read(dd.zd, дд.мц, дд.дн ); д г.гд : = 1583; д г.м ц : = 1; д г.д н : = 1; д е н : = ден + дни(гд, дд)\ магомет(ден, дл)

печать результатов end. Процедура м агом ет очень похожа на процедуру б уд у­ щее солнечного календаря (см. задачу 61). Поскольку нам остается только приложить записанные в ней вычисления к лунному календарю, мы не будем пояснять ее детальнее.' Действия, указанные во втором прямоугольнике, записы• ваются на языке Паскаль следующим образом: (* число дней от 16.07.622 до 01.01.1583 *) ден : = 16 (* число дней до конца июля 622 г. *) + 3*31 + 2*30 (* + д о конца 622 г. *) + (1583 — 623)*365 (* + до конца 1582 г.*) for ad: = 6 2 3 to 1582 do (* учитывая *) (* високосные годы *) (* по юлианскому календарю *) if гд mod 4 = 0 then ден : = д е н 1; ден : — д е н — 10 (* и реформу календаря *) Действия третьего прямоугольника совсем просты— печать дат солнечного календаря и соответствующих им дат лунного календаря. Соединив все части, получим законченную программу: program лунный (input, output ); type г о д = 1583..5000; (* григорианский календарь*) м е с я ц = 1..12; ден ь — 1..31; д а т а = record 166


г д :г о д ; м ц :м еся ц ; д н : день end; д атал ун = record (* дата лунного календаря *) г д : 1 ..5000; м ц : 1..12; д н : 1..30 end; var дд, (* заданная дата григорианского календаря *) д г :д а т а ; (* 01.01.1583*) д л :д а т а л у н ; (* искомая дата лунного *) (* календаря *) d e n :in te g e r; (* число дней от начала *) (* лунного года до дд *) function високос (г д :г о д ) :boolean; (* является ли год гд високосным1*) ... (см. задачу 59) function дмес (г д :г о д ; м ц :м е с я ц ):Ш е § е г; (* сколько дней в месяце мц года гд *) ... (см. задачу 59) function дни(д 1, d 2 :d a T a ):in te g e r; (* число дней от даты сН до <92 *) ... (см. задачу 62) pioiedure м агом ет ( д '.integer; var дл '.д атал ун ); (+ дата лунного календаря — *) (* д (д > 0 ) дней спустя после бегства Магомета *) var д м е с : 29..30; (* число дней лунного месяца *) function дгод (e d :in te g e r ):in te g e r ; (* сколько дней в лунном году гд *) begin if (гд mod 30) in [2, 5, 7, 10, 13, 16, 18, 21, 24, 27, 29] (hen д го д : = 355 else д го д : = 35 4 end; In fin (* м агом ет *) д л .гд : = 1; while д > д го д (д л .гд ) do begin д : —д — дгод (дл.гд); д л .гд : = дл .гд + 1 end; <) л . мц : = 1;

дм ес: = 30; 167


while д > д м е с do begin д : — д — дмес\ д л .м ц : = дл.мц + 1 ; if ойй(дл.мц ) then дм ес: = 3 0 else д м ес: = 2 9 end; д л .д н : = д end; begin (* лунный *) (* число дней от 16.07.622 до 01.01.1583 *) ден : = 16 (* число дней до конца июля *) ^ 022 г ^ + 3*31+ 2*30 (* + д о конца 622 г. * + (1583 —623)*365 (* + до конца 1582 г .* for г д : = 6 2 3 to 1582 do (* учитывая*) '(* високосные годы по юлианскому *) (* календарю *) if гд mod 4 = 0 then д е н : = ден + 1; ден : = д ен — 10; (* и реформу календаря*) read (дд.гд, дд.мц, дд.дн)-, дг. гд : = 1583; д г.м ц : = 1; д г.д н : = 1; д е н : = ден + дни(дг, дд)\ магомет(ден, дл)\ д д .м ц : 1, дд.гд-А ); w rite(dd.dn , w rite(U O ЛУННОМУ КАЛЕНДАРЮ’); >5 1У хюг'йе(дл.дн:Ъ, . д л .м ц : 1, . , дл.гд'А ) end. (* лунный *) После того как этой программе были предъявлены ис­ ходные данные — дата 1 января 1987 г., ЭВМ напечатала 1.1.1987 ПО ЛУННОМУ КАЛЕНДАРЮ 29.4.1407 © Составьте программу, которая бы устанавливала д а ­ ту солнечного календаря, соответствующую указанной дате лунного календаря. 0 © Составьте процедуру для нахождения числа дн от одной даты лунного календаря до другой.

69. Рисование геометрических фигур Можно без,труда составлять программы, в соответствии с которыми ЭВМ при помощи своих символов будет «ри­ совать» различные геометрические фигуры. Приведем программу, которая печатает прямоугольник, состоящий из символов + , —, 1. Исходные данные — вы168


сота и длина прямоугольника (выражены числом симво­ лов). • ,.j . program прямоугольник (input, output)', const m a x = 120; (* ширина используемой бумаги*) var a, b :3 ..m a x ; (* стороны прямоугольника*) /: in te g e r ; procedure горизонт (длuнa:integer)^, (* построение горизонтальной стороны *) var k '.integer-, begin w rite ( + ’); for k : = \ to длина —2 do w rite (’ — ’); w riteln ( + ) end; begin read (a, b); горизонт (b); for /: = 1 to a —2 do w riteln ( T , T : b — 1); горизонт (b) end. При a = 5, a b = 8 ЭВМ напечатала прямоугольник, приведенный на рисунке 21. •' (-оставьте программу, которая печатала бы ромб, з а ­ ношенный звездочками. Входное дан ное— длина стороны ромба. (•')<•) Составьте программу, которая бы печатала равногмчфенный треугольник, заполненный звездочками. Входное миное— высота треугольника в звездочках.

70. Квадрат из цифр Интересным в своем роде является квадрат, составлен­ ный из чисел отО до п (рис. 22). < '.оставим программу для печати этого квадрата чисел (Я входное данное, причем rasgC9). Прежде всего обратим внимание вот на что. Если бы мы н.исртили систему координат, центр которой совпадал бы ( .....|>рой 0 (центром квадрата), а оси были бы параллельны i тронам квадрата, то в точке (г, /) надо было бы печатать ■ии .но, представляющее собой больший из модулей коорди­ 169


+--------+ I I I

f r I

+--------+ Рис. 2 1 ,

n . . . nnnnn. . . n

.

нат (г, /). Это можно выразить посредством такого условного оператора: if a b s (i)> a b s (j) then w rite (abs (i): 1) else w rite (ab s (/): 1) Все позиции (г, /), в которых — n ^ i ^ i n , — n ^ j ^ . n , просмотрим при помощи ДВОЙНОГО цикла:

.

n . . 22222 . ;n n . . .21,112.. ,n n . . . 2 1 0 1 2 . . .n n . . . 2 1 1 1 2 . . .n n . . . 2 2 2 2 2 . . .n

n . . . nnnnn. . .n Рис. 2 2. Квадрат цифр.

n.....2 .. .n n... 2 1 2 .. .n n . . . 2 1 0 1 2 . . .n n . . . 2 1 2 . . .n n... 2 ... n

for г : = — п to п do for j : = — п to п do Таким образом, вся программа выглядит так: program к в а д р а т (input, output)-, var п : 0..9; i, / : — 9..9; begin read(n)\ for i : — — n to n do begin for j : = — n to n do

if a b s (i)> ab s(j) then w rite (abs(i): 1) else w rite (ab s(j): 1); w riteln end end. © Напишите программу, которая печата­ ла бы шахматную доску такую, чтобы у нее Рис. 23. черные поля представляли собой квадраты из Р ом б цифр. цифр указанного вида, а белые состояли из пробелов. Входное данное — максимальное число п. © © Составьте программу для печати ромба из цифр (рис. 23). Входное данное — цифра п ( п ^ 9).

71. Симметричные фигуры Из отдельных символов можно составить различные ри­ сунки. Программы, печатающие такие рисунки, совсем простые. Достаточно записать символы, входящие в рисунок, в оператор печати. Если ж е в расположении символов в ри­ сунке наблюдаются определенные закономерности, то откры­ вается возможность составлять более интересные программы печати рисунка. 170


* * * *

* * *

*

* * # * # *

*

*

*

*

*

к- * * * If * * * * * * *

*

* *

*

*

*

*

*

* * * * * *

*

*

*

*

*

* *

*

* * * * * * * * * * * *

*

* * * * * * *

* * * * * * it- * * * *

*

* * *

* * *

*

*

*

* * * * *

*

*

*

*

* * * * *

* * Мне, 24. С и м м етри ч н ая ф игура.

Рис. 2 5 . Ф игура ( а ) и зе р ­ кальн о е ее о траж ен и е по о т ­ ношению к оси о р ди н а т (б )

Например, интересно составлять программы печати симн' гричных рисунков. В этом случае необходимо ввести только млеть рисунка, а весь рисунок можно получить при помощи ■шиветствующих трансформаций (отражений, повторов) «поденной части. Например, для того чтобы нарисовать фигуру, изображенную на рисунке 24, достаточно было бы располагать только его частью, находящейся внутри квад|>л1л, а всю фигуру целиком мы получили бы, поворачивая н у масть вокруг центра на 90°, 180°, 270° или же посредством макального отражения ее относительно оси ординат, а за11*м зеркального отражения обеих полученных частей от­ носительно оси абсцисс и т. д. Таким образом, для того и uhi.i рисовать симметричные фигуры, важно уметь отраuпп. имеющуюся часть фигуры и поворачивать ее на ка­ ши! иибудь угол. Составим процедуры для выполнения этих игПствий. Входное данное — квадратная картинка (она проще, не|' hi прямоугольная). Определим тип картинки: type строка = a r r a y [1 ..re] of char; карти нка = a r ra y [l..re] of строка I’.I iмер картинки re представляет собой определяемую конI Iлиту. |'.оставим процедуру для получения зеркального отражеми.1 шданной фигуры относительно оси ординат. Зеркальп." отражение фигуры приведено на рисунке 25. |innc(lure отраж ение (а'.к ар ти н к а; var b ’. кар ти н ка); || отражение по отношению к оси ординат *) 171


var i, j'.integer-, begin for i : = 1 to n do for j : = 1 to n do b [i, j |: = a [i, n — j+ A ] end

Теперь составим процедуру, поворачивающую фигуру на 90° против часовой стрелки: procedure поворот (а :к а р т и н к а ; var b : картинка)-, (* поворот на 90° против часовой стрелки *) var i, j'.integer-, begin for i : = 1 to n do for j : — 1 to n do b [i, j ] : = a [ j , n — i + 1] end Различные комбинации этих процедур позволяют нам по­ лучить разнообразные симметричные рисунки. © Используя процедуры отраж ение и поворот, составьте процедуру зеркального отражения относительно оси абс-i цисс. © © Составьте процедуру, которая получала бы из кар ­ тинки (входного данного) ее негатив, т. е. вместо символа^ оставляла бы пробел, а вместо пробела печатала бы к а ­ кой-нибудь другой символ (например, ’н ’). © © © Составьте процедуру, которая увеличивала бы заданный рисунок в п раз.

72. Орнаменты Путем многократного повторения небольшой фигуры и ее разнообразных отражений можно получить красивые орна­ менты. Они могут пригодиться, в частности, любителям рукоделия. Составим программу, которая из небольшого исходного рисунка будет создавать орнамент. Будем придерживаться следующего порядка: сначала осуществляется зеркальное отражение исходной фигуры относительно оси абсцисс, затем — зеркальное отражение полученной фигуры отно­ сительно оси ординат. После этого получившаяся фигура повторяется m раз по горизонтали (вправо) и п раз — по вертикали (вниз). Исходные данные программы — фигура размером l-h символов и числа т и п (причем 1 ^ т ^ 7 , 1 0 * ^ 5 ) . 172


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

Определим тип исходной фигуры (трафарета): type строка — a r ra y [1../] of char; траф арет = a rr a y \\..h] of строка; (/ ширина траф арета, a h — вы сота). Д ля того чтобы напечатать первую строку орнамента, необходимо m раз напечатать элементы массива е, относя­ щегося к типу строка:

е [1], е [2], ..., е [/], е [/], е [7— 1], ..., е [1], и повторить это m раз. Аналогичным образом печатаются и оп альн ы е строки орнамента.

Приведем законченную программу: piogram орнамент (input, output ); const 1= 8; h = 8; (* ширина и высота трафарета *) m m ax = 7; nm ax = 5; (* ширина и вы с о та* ) (* самого большого орнамента *) type строка = &гг&у [1../] of char; траф арет — a r r a y [1 ..h) of строка; v ar карт'.тр аф арет; m, п, (* ширина и высота орнамента *) k : in teg er; i : 1..h; l -Л.Л; procedure печетр (d A u n a:in teger; е:стр о ка); var i, j'.in te g e r;

begin for г: = 1 to длина do begin for j : = 1 to / do (* печать строки *) w rite (e[jj); (* трафарета и его *) for /: = 1 downto 1 do (* отражения no*) w rite (e [/']) (* отношению к оси *) (* ординат *) end; w riteln n ld ; (* печетр *) in у III (* орнамент *) : tot i : — 1 to h do (* ввод исходной фигуры *) begin

573


хххххХххххххххххххххХХххххххххххххххххххххххххххххххххххххххх ХХХХХХХХ X X ХХХХХХХХ X X ХХХХХХХХ X X ХХХХХХХХ X I ХХХХХХ хххх

X XX

х

X X XX

хххХхх

X XX

хххх

X

ХХХХХХ

X X

хххх

X

хххх ХХХХХХ

X

ХХХХХХ

X <

> X XX

X

ХХХХХХХХ

X >

хххх

ХХХХ

<

ХХХХ

X

XX

XX

<

XX

X

<

XX

<

ХХХХ

< X

хххх X

XX

X X X X х х х х XX X XX X <X XX

XX

XX

X

ХХХХ

X X

X

XX

X

хххх

X >

ХХХХХХ

ХХХХХХ

ХХХХХХХХ

ХХХХХХХХ

X XX

<

X X X X

XX

X

ХХХХ

X

ХХХХХХ

X

ХХХХХХХХ

XХХХХХХХХХХХХXXXXXXХХХХХХххххххххххххххххххххххххххххххххххххххх XXXXXXХХХХXXXХХХХХХХХXXXXXхххххххххххххххххххххххххххххххххххххх X ХХХХХХХХ X X ХХХХХХХХ X X ХХХХХХХХ X X ХХХХХХХХ X X X

ХХХХХХ хххх

X XX

X

XX

ХХХХХХ

X XX

хххх

X

XX

ХХХХХХ

X XX

X

ХХХХХХ

XX X X

хххх

XX

X

XX

хххх XX

хххх

хххх

хххх

XX

XX

XX

ХХХХХХХХ X

хххх

X X

XXX

X X

XX

х

XX X

X ХХХХХХХХ

X

хх

х

ХХХХ

X X

X

XX

X

ХХХХ

X X

X

XX

X

ХХХХ

ХХХХХХ

ХХХХХХ

ХХХХХХ

ХХХХХХХХ

ХХХХХХХХ

ХХХХХХХХ

X

хххх.ххх Х Х Х Х X X Х Х Х Х Х Х Х Х X Х Х Х Х Х Х Х Х х х х х х х Х х х Х Х Х Х Х Х Х Х Х Х Х Х X X X X X X X X X х х х х

а)

б)

Рис. 26. О рнамент.

for ] : = 1 to / do read (кар т [г, /J); read ln end; read (m, n)\ if m m ax) or (m < 1) then m : = т т а х ; if (n^> nm ax) or ( n d ) then n : = п тах\ for k : = 1 to n do begin for i : = 1 to h do печстр (m, карт [г]); for i : = h downto 1 do печстр (от, карт [/]); end end. Выполнив эту программу при заданной исходной фи­ гуре (рис. 26, а ) и при п = 2, m = 5, ЭВМ напечатала ор­ намент, приведенный на рисунке 26, б. 174


***

*

*** *****

* * * * * * * * * * * * *

* * * * ** * *

***

*

*

*

* * * ***

* *

* ***** *** ***** ***

*

**** ***** ***

***

*** * * * * * * * * * **** **** * 't * * • *

* * * * / * * * ***.* fc * *$**

* *****

*

*

***

* *

***

*

*

*

*,|

***

*

\ *

***

Рис. 27. Большие цифры, изображенные звездочками.

11одчеркнем, что исходные данные лучше всего предстаiuiti, наглядно: траф арет изобразить естественным образом и носьми строках. Для того чтобы перейти к новой строке, in пользуется оператор read ln . 1\\. Символы большого формата

Алфавитные печатные устройства печатают все символы ||того размера (формата). Большие символы, при помощи которых иногда печатается текст (например, заголовки), оыиают составлены из отдельных маленьких символов, поиоГшо тому как из различных лампочек образуются симиолы световой рекламы. < '.оставим программу, которая будет читать натуральные числа и печатать их при помощи больших цифр, как пока1нно на рисунке 27. В программе надо будет иметь сформи­ рованные образцы цифр. После этого можно читать строку 01 строкой исходные данные (числа) и печатать их при помощи больших цифр. ('.оставим такую схему программы: |iioffram заголовки {input, output); const 6 = 6; (* ширина цифры + один пробел*) h — 7; (* высота цифры *) type траф арет = a rra y [\..h] of packed a rra y [1.6] of char; цифры = a rra y [0..9] of траф арет; (* трафареты цифр *) var циф: цифры; п : in teg er; (* исходное данное *) procedure формирование (var циф: цифры); формирование образцов цифр procedure печать (п '.integer; циф'.цифры); печать числа п цифрами большого формата 175


begin формирование(циф); while not eof do begin read ln (n); печать(п , циф) end end. Образцы символов можно «нарисовать» при помощи констант — строк символов — и присвоить их элементам массива, изображающим эти образцы. Например, число 4 образуется так: циф [4,. 1] : = ’ X’ циф [4, 2 ] : = ' XX’ циф [4, 3] : = ’ X X’ циф [4, 4] : = ’ X X’ f циф [4, 5] : = X X’ циф [4, 6] : = ’Х ХХХХ’ циф [4, 7] : — ’ X’ Аналогичным образом можно образовать и остальные цифры. Мы получим длинную и неинтересную процедуру. Другой способ сформировать образцы цифр состоит в том, чтобы изображать их в виде исходных данных, пред­ шествующих действительным исходным данным (числам, которые требуется напечатать), и эти данные сохранять в- массиве. Такое формирование цифр приносит пользователю про­ граммы больше хлопот (необходимо вводить и образцы цифр), но больше и свободы (можно выбрать и другие образцы цифр). Приведем программу, составленную вторым способом: program заголовки (input, output ); const b — 6; (* ширина цифры + один пробел*) h = 7; (* высота цифры*) type траф арет = arra y [1 ..h] of packed a rra y [1..6] of c h a r ; цифры = a rra y [0..9] of траф арет; (* трафареты цифр *) var циф’.цифры; п : in teg er; (* исходное данное*) procedure формирование (var циф’.цифры); (* формирование образцов цифр *) var г: 1,./г; 176


с : 0..9; j:\ ..b ;

begin for i : = 1 to h do begin for c : = 0 to 9 do for j : = 1 to b do read (циф[с, i, /]); read In end; readln (* пустая строка исходных данных *) end; procedure печать (n :in te g e r; циф :циф ры); const m = 1 0 ; (* максимальное число цифр *) var число: a rra y [\..m] of траф арет; (* образцы цифр *) k , i, j'.in te g e r; begin (* печать *) for k : = 1 to m do for i : = 1 to h do число \k, i\: = ; (* b пробелов *) k : = m ; (* начинать с конца числа *) repeat (* записывание в массив число числа п *) (* большими цифрами *) число [1г\:=циф[п mod 10]; п : = п div 10; k: = k —1 until (n = 0) or (£ = 0); (* печать числа n большими цифрами *) for i : = 1 to h do begin for k : = 1 to m do for i : = 1 to b do w rite (число [k , i, /']); w riteln end; w riteln (* строка — пробел между соседними числами *) end; begin (* заголовки *) формирование (циф); while not eof do begin readln(n); печать(п, циф) end end.

177


© Составьте программу для печати букв большого фор­ мата.

74. Ч астота повторения букв В каждом языке одни буквы алфавита употребляются чаще, другие — реже. Например, установлено, что в англий­ ском языке чаще всего встречается буква е , второе место занимает буква t. Установить частоту употребления букв можно посредст­ вом анализа написанного на данном языке достаточно длин­ ного текста. Составим программу, позволяющую установить частоту повторений букв для русского языка. Запишем в массив, сколько раз к аж д ая буква алфавита повторяется в тексте: var d :a r r a y [ А .. Я ] of in te g e r ; Частоту повторений букв представим в процентах: для этого число повторений в тексте каждой буквы разделим на общее число всех букв из текста и умножим на 100. Приведем составленную программу: program буквы (input, output)', var d :a r r a y [ А .. Я ] of integer-, (* частота каждой буквы *) с : char-, (* исследуемый символ *) s'.integer-, (* число букв во всем тексте *) begin for с : = A to Я do d[c\: = 0 ; while not eof do begin read(c)\ s if с in [ А .. Я J then d [c] : = d [c] + 1 end; s : =0; for c : = A to Я do (* сколько всего букв в тексте*) s : = s + d[cj;

w riteln (’В ТЕКСТЕ, ДЛИНА КОТОРОГО’, s, ’БУКВ,’); w riteln (’ЧАСТОТА БУКВ ТАКОВА:’); for с : — A to Я do w riteln (с, d [c]*100/s: 10:2, ’ % ’) end. 178


<-> Измените представленную программу так, чтобы буквы (п их повторяемость в процентах) печатались в соответствии с числом их повторений в рассматриваемом тексте: перн.Iи буква, встречающаяся в тексте чаще всего, вторая — |.|, что по частоте употреблений занимает второе место, и т. д. Г.ели несколько букв в тексте встретилось одинаковое число I»;|ч, то безразлично, к акая буква будет написана первой. © © Составьте программу, которая подсчитывает часюту повторений двубуквенных сочетаний. Печатайте только и пары букв, частота которых не менее 10%. © © 0 Составьте программу, которая бы подсчитываi.i частоту повторений слов той или иной длины в выбран­ ном тексте: подсчитывала бы, сколько слов состоит из одной г>уквы, из двух, из трех и т. д.

75. Частота повторения слов Было бы интересно установить, сколь часто в лоданном п.I вход тексте встречаются одинаковые слова. Составим программу, позволяющую решить эту задачу, опираясь на рпультаты задачи 74. Будем считать, что в тексте не более ишхчсл различных слов, а самое длинное слово в тексте имеет |,пнпу не более чем т а х д с л букв. Для того чтобы поместить слово, определим упакованный массив — строку символов: type слово = packed a rra y [1 ..т а х д с л ] of char Слова и их частота (число повторений в тексте), записыплемые в массив, состоят из записей. Тип массива опрен ляется следующим образом: type частота = a rra y [1 ..т а х ч с л ] of record х : слово; d : in teger end Приведем составленную программу: |||<>|»гат слова (input, output)',

const т а х ч с л = 1000; (* максимальное число различных *) (* слов в тексте *) т а х д с л = 30; (* максимальная длина слова*) type слово = packed a rra y [\ ..тах д с л ] of char-, частота = a r r a y [1 ..т а х ч с л ] of record x : слово; d : in teger end 179


var р а з л : О .ж ахчсл; (* число исследованных различных *) (* слов *) всего; (* общее число исследованных слов *) k\ in teg er; дел, i:0..m axdcA ; с л : слово; ч аст'.часто та; с : char; н айден: boolean; begin в сего : ==0; р а з л : = 0 ; while not eof and (всего <C т а х ч е л ) do begin дел: = 0; read(c); (* чтение и образование слова *) while (с in [’А’...’Я’)) and (д с л < т а х д с л ) do begin д е л : — дел + 1; сл[дсл}: = с ; read(c) end; if д е л ф 0 then (* образованное слово*) begin в с е го : = всего 1; for i : = дел-+ 1 to т а х д е л do слЩ : = ’ (* в конце слова — пробелы *) (* поиск слова *) k : = 0 ; найден: — fa lse ; while ( k c e c e e o ) and not найден do begin k := k + \ ; найден: — част[к]'Х — сл end; if найден then 4acr[k\-d: — 4acT[k\-d + 1 else begin р а з л : = разл + 1; част [разл] ■х : — сл; част \разл\-а: — 1 end end end; (* печать слов и их частоты *) 180


for k : = 1 to разл do begin for i : — 1 to т а х д с л do w rite (част[1г\- x[i]); . w riteln (частЩ - d*100/ всего, ’% ’) end end.

© Приведенную программу измените так, чтобы она печлтал. а слова в порядке уменьшения их частоты. Слова, члстота которых меньше 10%, не печатайте.

76. Пилообразный текст Интересно располагать текст непривычным образом, на­ пример в форме пилы, елочкой или даж е в виде спирали. Составим программу, которая печатала бы тексты в форме милы. Печать будет происходить таким образом: в первой строке — одно слово, каж д ая следующая строка должна быть хотя бы несколько длиннее предыдущей. Когда очеред­ ная строка имеет заранее указанную максимальную длину, л текст не поместился в ней, снова повторяется то же самое начиная со строки, состоящей из одного слова,— печатается новый зубец «пилы». Приведем составленную программу: program "пила" (input, output); const р — 90; (* ширина "пилы" *) т а х = 30; (* максимальная длина слова*) v a r есть; (* сколько символов во вновь *) (* образуемой строке *) 6 b u o :in te g e r; (* сколько символов было*) (* в предыдущей строке *) с : ch ar; begin (* пила *) repeat (* печать "пилы" *) есть: = 0 ; repeat (* печать "зубца" *) было: = е с т ь ; есть: = 0 ; repeat (* печать строки *) repeat (* пропуск пробелов *) read(c) until (с ф while (с ф

) or eof; ) and not eof do 181


Он Рис. 28. Пилообразный текст. вступил в темные широкие сени, от которых подуло холодом, как из погреба. Из сенёй он попал в комнату, тоже темную, чуть-чуть озаренную светом, выходившим из-под широкой щели, находившейся внизу двери. Отворивши эту дверь, он наконец очутился в свету и был поражен представшим беспорядком. Казалось, как будто в доме происходило мытье полов и сюда на время нагромоздили всю мебель. На одном столе стоял даже сломанный стул, и рядом с ним часы с остановившимся маятником, к которому паук уже приладил паутину. Тут же стоял прислоненный боком к стене шкаф с старинным серебром, графинчиками и китайским фарфором. На бюре, выложенном перламутною мозаикой, которая местами уже выпала и оставила после себя одни желтенькие желобки, наполненные клеем, лежало множество всякой всячины: куча исписанных мелко бумажек, накрытых мраморным позеленевшим прессом с яичком наверху, какая-то старинная книга в кожаном переплете с крась обрезом, лимон, весь высохший, ростом не более лесного ореха, отломленная ручка кресел, рюмка с какою-то жидкостью и тремя мухами, накрытая письмом, кусочек сургучика, кусочек где-то поднятой тряпки, два пера, запачканные чернилами, высохшие, как в чахотке, зубочистка, со ве рш ен но .п ож ел тев ша я, которою хозяин, может быть, ковырял- в зуьах своих еще до нашествия на Москву французов. (Гоголь Н.Б. Мертвые души)

begin (* ввод и печать слова *) write(c); е с т ь : — есть + 1; read(c) end; w rite ( ); (* оставляемый пробел после слова*) есть: = есть + 1 u n til (е с т ь > б ы л о ) or eof; w riteln until (е с т ь > р — т а х ) or eof until eof end. Эта программа печатает весьма несовершенную "пилу" — "зубья" имеют неодинаковую длину и ширину На рисунке 28 приведена форма, в которой программа "пила" расположила заданный текст. 182


© Составьте программу, которая будет печатать вводи­ мый текст следующим образом: в первой строке было бы

одно слово, во второй — два, в третьей — три и т. д. Если к'кст не закончится, когда мы дойдем до строки заранее определенной длины, начните опять с начала. Знаки препи­ нания набираются непосредственно рядом со словом без пробела. Например: человек живет настоящую жизнь, если счастлив счастьем других (Гёте). © © Составьте программу, которая будет печатать вво­ димый текст елочкой, т. е. так, чтобы "зубья" "пилы" были с обеих сторон.

77. Билистинг Иногда весьма удобно напечатать текст программы в двух расположенных рядом экземплярах, т. е. получить би­ листинг. Условимся: длина строки исходных данных (программы или какого-нибудь другого текста) не более 55 символов (чтобы оба экземпляра листинга поместились на листе бу­ маги). Второй экземпляр программы начнем печатать с 61-й позиции. Приведем составленную программу: program билист (input, output ); const длин = 55; (* максимальная длина строки *) проб = 5; (* число пробелов между экземплярами *) var с т р : a rra y [1 ..длин) of char; k, д л и н а :!..д л и н ; begin wrile not eof do begin дли на: = 0 ; while not eolti do begin д л и н а: = длина + 1; геас1(стр[длина ]) 1 end; re ad In; for 1 г:= д л и н а -\ - 1 to длин do CTp[k):

= ’ ’;

183


for k\ = \ to длин do (* 1-й экз.*) w rite (crp\k ]); w rite ( : проб ); for k : — 1 to длин do (* 2-й экз. *) w rite (стр[к ]); w riteln end end.

Выполнив данную программу, когда исходные данные — это сама программа билист, ЭВМ напечатала билистинг, приведенный на рисунке 29. 0 Составьте программу, которая печатала бы п-листи (определите константу п и соответствующую ей длину строки).

78. Техническое редактирование текста Следует позаботиться, чтобы напечатанный текст выгля­ дел красиво: чтобы пробелы между словами были одина­ ковыми, заголовки находились посередине текста и т. д. Для этого создаются программы технического редактирова­ ния текста. Приведем программу, которая разделяет текст на строки program билист ( in p u t, o u tp u t); c o n s t длин = 5 5; <* максимальная длина строки *) проб = 5 ; (* число пробелов иежду экземплярами *) v a r стр : a r r a y [ 1 . . длин] o f c h a r ; k , длина : 0 . . длин; b egin w h ile not e o f do begin длина := 0 ; w h ile n o t e o ln do begin длина := длина + 1; re a d (с т р [длина]) end; re a d In ; f o r k := длина + 1 to длин do стр [k] := * ’ ; f o r k := 1 t o длина do (* 1 - й э к з. *) w r i t e (с т р [ k ]) ; w r i t e ( ’ ' : проб); fo r k := 1 to длин do (* 2 - й эк з. *) w rite (C T p [k ]); w riteL n end end. Рис. 29. Билистинг.

184


желаемой длины и последнее слово в каждой строке подтяI имает к правому краю, делает одинаковыми пробелы между е.повами. Таким образом текст располагается во всех газетах и журналах. Для простоты не будем требовать, чтобы программа переносила слова из одной строчки в другую. Группы символов, между которыми в исходном тексте (и исходных данных) нет пробелов, будем считать неделимы­ ми словами. Например, в тексте: «О, какой счастливый 1990 год!» ;— Оудет 5 слов. Запятая принадлежит первому слову «О,», л восклицательный знак — последнему: «год!». Какое слово будет последним в строке и на сколько позиций его следует сдвинуть вправо, выясняется только по прочтении строки указанной длины. Однако, чтобы сделать одинаковыми пробелы между словами, иногда необходимо сдвинуть вправо не только последнее, но и другие слова и данной строке. Часть прочитанного текста (строку ук азан ­ ной длины) следует сохранить в буфере — в массиве симво­ лов, после этого можно подсчитать пробелы между слова­ ми, сдвинуть к краю строки слова и напечатать их. Сдви­ гать слова с одного края на другой можно и в про­ цессе печати, вставляя между словами дополнительные пробелы. program билист (input, output ); const длин =55; (* максимальная длина строки *)

проб = 5; (* число пробелов между экземплярами *) var стр : array [1..длин] of char;

к, длина : 0..длин; begin while not eof do begin

длина := 0; while not eoln do begin

длина := длина + 1; r e a d (стр[длина]) end; readIn; for k := длина + 1 to длин do стр [k] := for k := 1 to длина do (« 1- й экз. *) write(CTp[k]I; write : проб); for k := 1 to длин do (* 2- й экз. *) write(CTp[k]); writeln end end.

185


Так мы и будем делать. Таким образом, выясняется схема программы. Она должна была бы состоять из двух процедур: одна читает исходный текст и заполняет буфер, другая печатает текст, находящийся в буфере, и вставляет пробелы. В основной части программы к этим процедурам следует обращаться попеременно до тех пор, пока не закончится исходный текст. Приведем программу: program редактор (input, output ); const длина 1 = 5 1 ; (* длина строки- f l *) длина2 — Ъ2; (* длина строки + 2 *) var буф\ arra y \\..длина\] of ch ar; (* буфер *) с '.char: (* индекс буфера *) b, z, (* индекс последнего символа слова *} 1г:0..длина2; (* число слов в строке *) procedure чтение; v ar с с : char; begin (* чтение *) k := 0 ; while not eof and ( b < длина 1) do begin сс: = c ; (* запоминается прежний символ *) read (c);\ if (с с ф ' ’) or ( с ф ' ’) then begin (* пишется в буфер *) b:

= b + ' 1; буфЩ : = c end; if (с с ф ’) and (c = ’) then begin (* конец слова *) k : = k + l; z : —b — 1 end end end; (* чтение *) procedure печать; var t, (* необходимо напечатать еще t пробелов *) tt, (* число дополнительных пробелов *) (* между соседними словами *) /, 1'.длина 2 ; begin (* печать *) if not eof then t: —длинаХ — 1 — 2 else (* слова в последней строке *) t : = 0 ; (* не следует сдвигать *)

186


for г: = 1 to 2 do begin c : = буф [/]; w rlte(c); if с = then (* слово кончается *) begin (* печать дополнительных пробелов *) k : = k — 1; tt : = t div k; for j : = 1 to tt do w r ite ( ’); t : — t —- tt end end; , _ it ; b : = 0; c: = for i : = z + 2 to длина 1 do begin (* начало непоместившегося слова *) (* переносится в начало буфера и *) (* будет началом новой строки *) с; = б уф [г]; Ь := Ь + 1 ; буф[Ь ]: = с end end; (* печать *) begin (* редактор *) b: = 0 ; с : = ’ repeat чтение; печать; w riteln until eof end. Обе процедуры ( чтение и печать) используют одни и те же данные (переменные). Поэтому мы описали их в основной части программы, а процедуры составили без параметров. Процедура чтение собирает прочитанный текст в буфере, оставляя только по одному пробелу между соседними сло­ вами. Поскольку буфер на один символ длиннее, нежели печатаемая строка, то, взглянув на буфер, мы можем ска­ зать, прочтено ли последнее слово полностью (тогда послед­ ний символ буфера — пробел) или только частично. Процедура печать печатает все находящиеся в буфере символы: от первого до последнего символа последнего целого (вошедшего полностью) слова. Кроме того, между словами вставляются дополнительные пробелы. 187


Если в конце буфера была часть слова, она не печатается, а переносится в начало буфера. После этого процедура чтение читает конец слова, новое слово и т. д. Текст отредактирован красиво, если в строке помещается по крайней мере два соседних слова. Если в строке помести­ лось только одно слово, то пробелы остаются после него. Если слово не помещается в строке, то оно переносится как угодно. Исходные данные и результаты (технически отредактиро- I ванный текст) данной программы приведены на рисунке 30.

79. Голландский флаг Это довольно популярная задача по программированию [5], формулировка которой связана с цветами голландского флага. Камешки: красные, белые, синие — располагаются в стол­ бик. Надо упорядочить камешки таким образом, чтобы их цвета шли в том же порядке, что и цвета голландского флага — красные, белые, синйе. Предположим, что камешки пронумерованы в порядке их следования сверху вниз. Камешками ведает робот. Он может выполнять две команды: 1) определить цвет г'-го (1 ^л.<!га) камешка и 2) поменять местами г-й и /-й камешки ( 1 ^ г , j ^ n ) , п — число камешков в столбике. Роботом управляет вмонтированный в него микропроцес­ сор. Напишем процедуру, посредством которой робот мог бы расположить камешки в порядке цветов голландского флага. В процедуре мы будем использовать обращение к функции цвет и процедуре зам ен а, которые соответствуют упомянутым командам робота. Их заголовки таковы: function цвет (i:in t e g e r ) :u , b\ procedure зам ен а (i, j:in teger)-, Тип цвета камешка определяется следующим образом: type ц в — (красный, белый, синий)-, Вмонтированный в робота микропроцессор имеет неболь­ шой объем памяти. Поэтому в ней могут храниться цвета лишь небольшого числа камешков. Кроме того, установле­ ние цвета продолжается долго, поэтому недопустимо много раз «смотреть» на один и тот же камешек. Как же лучше рассортировать камешки? Прежде всего следовало бы определить область тех камешков, которые 188


Я пошел в направлении леска, повернул ннираво, забирал, все забирал, как мне советовал старик, и добрался наконец, (ID большого села с каменной церковью и новом вкусе, то есть с колоннами, II обширным господским домом, тоже с колоннами. Еще издали, сквозь частую ■ нтку дождя, заметил я избу с тесовой м>ышей и двумя трубами, повыше других, по Пссй вероятности жилище старосты, куда и и направил шаги свои, в надежде найти у ного самовар, чай, сахар и не совершенно кислые сливки. В сопровождении моей продрогшей собаки взошел я на крылечко, в сени, отворил дпсрь.но вместо обыкновенных принадлежностей и (бы, увидал несколько столов, заваленных бумагами, два красных шкафа, забрызганные чернильницы, оловянные песочницы в пуд веса, длиннейшие перья и прочее. На одном ил столов сидел малый лет двадцати, с пухлым и болезненным лицом, крошечными глазками, кирнын лбом и бесконечными висками. Одет он был, как следует, в серый нанковый кафтан с глянцем На воротнике и на желудке. (Тургенев И.С. Записки охотника)

а)

Я пошел в направлении леска, повернул направо, забирал, все забирал, как мне советовал старик, и добрался наконец, до большого села с каменной церковью в новом вкусе, то есть с колоннами, и обширным господским домом, тоже с колоннами. Еще издали, сквозь частую сетку дождя, заметил я избу с тесовой крышей и двумя трубами, повыше других, но всей вероятности жилище старосты, куда я и направил шаги свои, в надежде найти у него самовар, чай, сахар и не совершенно кислые сливки. В сопровождении моей продрогшей собаки взошел я на крылечко, в сени, отворил дверь, но вместо обыкновенных принадлежностей избы, увидал несколько столов, заваленных бумагами, два красных шкафа, забрызганные чернильницы, оловянные песочницы в пуд веса, длиннейшие перья и прочее. На одном из столов сидел малый лет двадцати, с пухлым и болезненным лицом, крошечными глазками, жирным лбом и бесконечными висками. Одет он был, как следует, в серый нанковый кафтан с глянцем на воротнике и на желудке. (Тургенев И.С. Записки ох от ни ка)

б; Рис. 30. П ервоначальны й текст ( а ) и то т ж е самы й технически отредакти р ован ны й текст ( б ) .


Область красных камней nr Камни неопределенного цВета nb Область белых камней

nm Область синих камней

п Рис. 31. Схема сортировки камешков.

не надо менять, поскольку они находятся на своих местах. Это все красные камешки, находящиеся вверху столбика, и все синие камешки — внизу столбика. Номер первого свер­ ху не красного камешка обозначим нк(п г), а номер первого снизу не синего камешка — не ( п т ) . Тем самым эти номера определяют область красных й синих камешков. Сверху от синих камешков должны быть белые. Первый не белый камешек, находящийся сверху от синих, обозначим нб(пЬ). Камешки, начиная с нб и кончая нк, составляют чет­ вертую область — неясного цвета (рис. 31). Меняя друг с другом камешки нк, нб, не и тем самым сокращая об­ ласть камешков неясного цвета, можно постепенно решить задачу. Приведем схему соответствующей процедуры: procedure флаг ( п :integer)-, (* п — число камешков в столбике *) var нк, (* не красный *) нб, (* не белый *) н е '.in teger; (* не синий*) begin установление номера нк первого сверху не красного камешка 190


установление номера не первого снизу не синего камешка н б : = не; while н к ^ н б do begin case ц вет(н б ) of красный:

камешек нб меняется с камешком нк; н к : = нк + 1 ;

белый:

н б: — н б — 1;

синий:

камешек не меняется с камешком нб; н е: = не — 1; нб: = н б — 1

end end end Записав указанные в прямоугольниках действия на языке Паскаль, получаем всю процедуру: procedure ф лаг (n :in te g e r ); (* п — число камешков в столбике *) (* не красный *) var нк, нб, (* не белый *) н е '.in teger; (* не синий*) begin н к: — 1; while цвет (н к ) — красный do н к: = н к-\-\; н е: —п; while цвет (не) — синий do н е : — не — 1; н б : ==не; while н к ^ н б do case цвет(нб) of красн ы й : begin зам ен а(н к, нб); н к: = же + 1 191


белы й: синий',

end; нб: = н б — 1; begin зам ена(нб, не); н б: = н б — 1; н е: = н е — 1 end

'

I

end end © Составленная процедура флаг удобна тем, что нет необходимости запоминать установленные цвета камешков. Но у нее есть и недостаток. Цвета некоторых камешков приходится устанавливать по два раза. Составьте более совершенную процедуру, устанавливаю­ щую цвет каждого камешка лишь по одному разу. © © Для задачи «Голландский флаг» составьте проце­ дуру, которая управляла бы роботом, способным менять местами только два соседних камешка.

80. Сортировка данных

Часто удобно иметь данные (особенно когда их много) расположенными в порядке возрастания или в порядке убы­ вания. Такое упорядочение в программировании зовется сор­ тировкой. Сортировать можно любые данные — важно толь­ ко, чтобы их можно было тем или иным образом сравни­ вать (устанавливать, какое из них является наименьшим).: Сортировать можно, например, значения скалярных типов, символы, упакованные массивы символов (строки). Сортировка применяется во множестве задач. Например, слова в словаре или фамилии в списке располагаются в алфавитном порядке. И вообще, для того чтобы легче находить то или иное данное в большом массиве данных, следует их рассортировать (см. задачу 75). Поэтому, в программах часто применяют сортировку. Есть различные методы сортировки. Одни из них простые, но более медлен- ! ные, другие быстрые, но более сложные. Одни сортируют ■ ! каждый массив заново, другие распознают уже упорядочен­ ные части массива и потому работают быстрее. Видный специалист по программированию Д. Кнут посвятил сорти­ ровке и поиску данных почти весь второй том «Искусства ; программирования» [7]. Составим процедуру, годную для того, чтобы данные любого типа рассортировать в неубывающем порядке. В а ж ­ но, чтобы на этом типе были заданы операции сравнения 19 2


(хотя бы одна, например С ) . Предназначенные для сор­ тировки данные следует записать в массив следующего типа: type м ассив = a rra y [1 ..m ax] of данное Результатом процедуры сортировки должен быть тот же самый массив — только его элементы должны быть распо­ ложены в порядке от наименьшего к наибольшему. Приме­ ним один из самых простых методов сортировки — метод отбора. Его суть: находится наименьший (точнее, не превышаю­ щий всех прочих) элемент массива и меняется местами с первым; затем в части массива, начинающейся со второго элемента, снова ищется наименьший элемент и обменивается со вторым и т. д. Когда обменяется последний элемент, массив считается упорядоченным. Записав эти действия на языке Паскаль, получим следующую процедуру: procedure отбор (var л'.'.массив; d A u n a :itite g e r ) ; (* сортировка методом отбора *) var м ал :д ан н о е ; (* наименьший элемент*) (* из части массива, не подвергшейся сортировке *) mm, (* индекс этого элемента *) k, j:in t e g e r : begin for k : = 1 to длина — 1 do begin (* элементы x [г], i<Ck, упорядочены и не превышают *) (* элементов х [/], д л и н а*) м а л : —x[k\, m in : — k\ for /: = k + 1 to длина do if х[/]<лшл then begin (* нахождение наименьшего элемента *) (* в неподвергшейся сортировке части м ас­ сива *) м а л : = х [/]; m in : = j end; х [m in]: = *[£]; (* найденный элемент м ал *) x [k ]: — м ал (* меняется с первым *) (* элементом x[k] не подвергшейся сортировке части *) end end (* отбор *) 7 В. А. Дагене

193


Использование в процедуре параметра длина составляет условие того, чтобы сортировать не весь массив, а только его часть (когда длина < m a x ) . Чтобы был рассортирован весь определенный массив, к процедуре отбор следует об­ ращаться следующим образом: отбор (х, max) Если мы захотим использовать эту процедуру для упо­ рядочения массива целых чисел, то тип данное в программе надо будет определить следующим образом: type данное = in teg er Если захотим упорядочить слова по алфавиту, то тип д ан ­ ное надо будет определить так: type данное = packed a r r a y [I..длин] of char (константа длин определяется таким образом, чтобы она была не меньше чем число букв в самом длинном слове). 0 Составьте процедуру, которая бы упорядочивала мас­ сив анкет так, чтобы фамилии были расположены в ал­ фавитном порядке. Тип анкеты в программе определяется так: type данное = record фамилия имя: packed a r r a y [1..15] of char\ годрож д: 1900 .. 2000 end © © Исходные данные — последовательность целых чи­ сел, содержащ ая не более 1000 членов. Создайте программу чтения этих чисел и печати их в неубывающем порядке. 81. Сортировка методом «пузыря» При выполнении программы, приведенной в задаче 80, в неотсортированной части массива находится наименьший элемент и переносится в начало этой ч асти — словно бы погружается под большие. Можно поступить и противопо­ ложным образом наибольшие элементы поднимать на по­ верхность. Так и происходит при сортировке методом «пу­ зыря». Поочередно сравниваются соседние элементы: х{\] с л'|2|, х[2\ с х |3j, x|3j с х[4] и т. д. Если x\ i— \\>x{i\, то элементы меняются местами: i -м элементом становится (I— !>й элемент, который опять сравнивается уже с элемен­ том x\i-\- 1]. Таким образом, как пузыри в воде, большие 194


элементы быстрее всплывают на поверхность, а с д мы^ , шой — быстрее всех. больТип массива определим так же, как и в задач Составим процедуру: е procedure «п узы рь» (var х :м а с с и в ; длина ’. in te g e r ) ■ (* сортировка методом «пузыря» *) var а'.дан н ое; j, k : in teg er; за м е н а : boolean; begin k : длина; repeat (* элементы x [t], i > k , упорядочены и не мецЬше (* чем элементы x[i\, i = 1, 2, ... , k *) >’ *' за м е н а : = fa lse ; (* пока обменов не было *) for j : —2 to k do if x [j — 1]> * [/ ] then begin (* элементы меняются местами a : = x [j — 1]; x [ j — \ \ := x [j]; x[j}: —a ; з а м е н а : = tr u e end; k: —k —1 until not зам ен а end (* пузырь *) * Особенно важ на переменная обмен. Если массив упорядочен, то во внутреннем цикле не меняется мест ни одна пара элементов и значение этой переменной оста Э^И false. Это значит, что можно закончить сортировку g TCH применить процедуру к уж е упорядоченному циклу, внецЛ'1» цикл будет применяться только один раз, а внутренни«Нии длина — один раз. Если массив неупорядочен — Столько раз, сколько и в задаче 80. ‘ Же В этой программе мы упорядочили элементы от Наиме шего к наибольшему, с тем чтобы нагляднее было срак Ь" ние с пузырем. Можно упорядочить массив от наибольц,Не" элемента к наименьшему (тогда на поверхность всцлывя^0 бы меньшие элементы). ' * Ли © Рассмотрев приведенную процедуру « пузырь * вы ,, метите вот что: если при каком-либо просмотре не над0 бь!а" сдвигать элементы, находящиеся в конце просматривав? части, то и во время всех дальнейших просмотров их не на°И будет сдвигать. Учитывая это, усовершенствуйте прИвед До


ную процедуру (необходимо запомнить место последнего обмена, так как следующий просмотр достаточно будет выполнить лишь до этого места).

82. Быстрая сортировка Сортировать данные приходится часто. Поэтому надо ис­ кать самые быстрые способы сортировки. Составим процедуру, применяя так называемый метод быстрой сортировки. Применением этого метода неупорядо­ ченный массив сортируется намного быстрее, чем упорядо­ ченный. Если мы исследуем, каким образом упорядочивают мас­ сив уж е рассмотренные процедуры сортировки, то заметим, что при первом «обороте» внешний цикл ставит на свое место (в начало массива в процедуре отбор или в конец массива в процедуре «п узы р ь») один — наименьший или наибольший — элемент данного массива. После этого массив как бы распадается на две части: отсортированную (со­ держащую один элемент) и неотсортированную (которую составляют все прочие элементы). Когда цикл выполняется снова, то аналогичным образом сортируется (и снова разби­ вается на две части) неотсортированная часть массива. Продолжительность цикла прямо пропорциональна числу сортируемых элементов. Таким образом, она уменьшается медленно. Продолжительность цикла уменьшалась бы быст­ рее, если бы, вместо того чтобы отщеплять от массива один элемент, мы разбивали бы массив на две примерно равные части, например пополам. Как это сделать? Выберем не наименьший и не наибольший элемент м ас­ сива. Назовем его средним. Средний элемент действительно находится на своем месте, если все прочие элементы, на­ ходящиеся слева, не превосходят его, а находящиеся спра­ ва — не меньше его. После такого упорядочения массива средний элемент можно больше не трогать, а часть мас­ сива, находящуюся слева, и часть, находящуюся справа, сортировать отдельно. Таким образом, самое в а ж н о е — найти место для среднего элемента. Это можно выполнить посредством следующей процедуры: procedure середина (д л и н а :in teg er; var х '.м асси в ; var с е р ’. in teg er); var лв, (* левый индекс *) п р ’. in te g e r; (* правый индекс*) procedure м енять (лв, п р '.integer); 196


элементы массива х [лв] и х [пр] меняются местами begin (* середина *) л в : = 1; п р : = длина; repeat while (х[лв]^х[рг]) and (лв<Спр) do п р := п р — 1; (* шаг справа по направлению к се­ редине*) м енять (л в, пр); while (х[уш]^л:[гср]) and (л в < .п р ) do л в : = лв + 1; (* шаг слева по направлению к середине *) менять (лв, пр) until лв = пр; с е р : = лв end (* середина *) Мы считаем, что массив определен так, как и в пре­ дыдущих задачах на сортировку (см. задачу 80): const m ax = . . .; (* максимальная длина массива *) type данное = . . .; (* любой простой тип *) м ассив = a rra y [1 ..m ax] of данное Как работает процедура середина, можно проиллюстри­ ровать на таком примере: 25 20 13 37 12 92 86 33 25 20 13 37 12 92 86 33 25 20 13 37 12 92 86 33 25 20 13 37 12 92 86 33 92 86 33 12 20 13 37 © 12 20 13 37 25 92 86 33 12 20 13 37 25 92 86 33 12 20 13 37 25 92 86 33 92 86 33 12 20 13 © © 12 20 13 25 37 92 86 33 В первой строке приведены восемь чисел — это исходный массив х. В последней строке тот же самый массив, когда процедура заканчивает работу. Подчеркнутые элементы — это те, на которые указывают индексы лв и пр, а обведен­ ные — элементы, только что поменявшиеся местами. Ввиду того что две полученные части массива, в свою очередь, могут быть длинными, к а ж д а я из них снова может быть подвергнута делению на две части. 197


Такую сортировку удобно программировать, применяя рекурсию. Приведем процедуру быстрый, которую мы получи­ ли, перестроив программу середина: procedure быстрый (лево, п р аво '. integer-, var х.: массив)]] var лв, пр '.integer-, с р : данное; (* средний *) begin л в : —лево-, п р := п р а в о ; ср : =х\лв\\ repeat while (х [п р ]^ с р ) and (л в с п р ) do пр: —п р — 1;{*шаг справа по направлению к середине *) х[лв ]: —х[пр\ while (х [л в ]^ с р ) and (л в < п р ) do л в : = лв + 1; (* шаг слева по направлению к середине *) х\пр\: —х\лв\ until лв = пр; j х[лв\: — ср-, if л е в о < .л в — 1 then быстрый (лево, л в — 1, х); if лв + 1 < право then быстрый (л в - 1-1, право, х) end При обращении к процедуре быстрый следует указывать индексы первого (л е в ) и последнего (п р ав ) элементов мас­ сива х, например быстр (1, m ax, х ) . Переставляемый на свое место элемент мы называем средним с известными оговорками: попадет ли он в середину массива, зависит от его значения. Когда исходный массив неупорядочен, то можно надеяться,.что значения его элемен­ тов располагаются случайным образом и упомянутый элемент окажется в различных местах массива. Если исходный м ае! сив упорядочен, то мы всегда будем попадать на тот или другой край массива и в таком случае данный метод буде.т работать ничуть не быстрее, нежели предыдущие методы (см. задачи 80 и 81). 0 Измените процедуру метода быстрой сортировки та-1 ким образом, чтобы на свое место попадал элемент м ас­ сива, находящийся в середине массива. Можно ли утверждать, что измененная таким-образом процедура всегда работает быстрее, чем процедура бы стр ?

83. Поиск Есть шуточный совет, как поймать в Африке льва. Разде­ лим Африку пополам. Ясно, что лев будет находиться только в одной половине. Та половина, в которой находится лев,. 198


снова делится пополам и т. д. Площадь Африки равна приблизительно 30 млн. км2. Следовательно, после примерно 45 делений мы получим площадку, не превышающую 1 м2. Теперь на такой площадке льва поймать нетрудно. Идею, воплощенную в данном совете, можно использо­ вать при поиске элемента в упорядоченном массиве. Делим массив пополам. Далее пополам делится та часть, в кото­ рой находится элемент и т. д. Поскольку данные упорядо­ чены (в порядке возрастания или наоборот), то, сравнивая искомый элемент со средним элементом массива, мы в состоя­ нии сказать, в какой половине находится искомый элемент. (К сожалению, установить, в какой из частей, полученных при делении Африки, находится лев, невозможно.) Составим функцию, которая проверяла бы, содержится ли искомая фамилия в представленном списке (массиве) фа­ милий, отсортированном по алфавиту. Значение функции бу­ дет равно номеру фамилии по списку; если фамилия не обнаружена, значение функции равно нулю. К аж дая фамилия — это строка (упакованный массив) символов, длину которой определяет константа длинафам. Длину массива (списка) фамилий определяет константа сдлина. Таким образом, типы исходных данных определя­ ются следующим образом: type ф амилия = packed a r ra y \\..длинафам] of с/гаг; список = a rr a y [1.. сдлина] of фамилия Приведем законченную функцию: function номер (х : список; ф : ф ам илия): in teg er; (* номер фамилии в списке *) var лево, право, сер ед и н а:in teg er; ес ть : boolean; begin (* номер *) номер: = 0 ; есть: = fa ls e ; (* фамилия пока не найдена *) л е в о : — 1; п р аво : = сдлина; repeat (* поиск фамилии в части массива *) (* от элемента ф [лево] до элемента ф [право] *) середина: = (лево -+- право) div 2; (* взятая часть*) (* делится пополам *) if ф < х [середина] then (* фамилия должна быть в левой части *) п р аво : = середина — 1 else if ф > х [середина ] then (* фамилия должна быть в правой части *) л е в о : = середина + 1 199


else (* удача — фамилия найдена *) begin е с т ь : — true; номер : = середина end until есть or (л е в о > право) end (* номер *)

84. Римские цифры Д ля записи римскими цифрами используются символы (латинские буквы) I, V, X, L, С, D, М, обозначающие соответственно числа 1, 5, 10, 50, 100, 500, 1000. При их помощи можно обозначать натуральные числа. Если большая цифра находится перед меньшей, то они складываются, а если меньшая — перед большей (в этом случае она не может повторяться), то меньшая вычитается из большей. Например, XL = 50 — 10 = 40; MCXXIV = 1000-f- 100+ 10 + + 10 + 5 — 1 = 1124МДифры М, С, X и I могут повторяться не более трех раз подряд. Все остальные цифры (D, L и V) — только по одному разу. Таким образом, самое большое число, обозначаемое римскими цифрами,— это MMMCMXCIX, т. е. 3999. Римляне записывали и большие числа. Буква m отмечала начало тысяч. Но мы будем рассматривать числа, не пре­ вышающие 3999. Т аб л и ц а 4

Римские цифры и их пары и соответствующие числа, записанные арабскими цифрами I

IV V

IX X XL

L

ХС

1

4

9

50

90

5

10 40

С

CD

100 400

D

СМ

м

500 900 1000

Если бы считали самостоятельными римскими цифрами не только вышеупомянутые отдельные символы, но и их пары IV, IX, XL, ХС, CD и СМ (см. табл. 4), то для перевода записи числа римскими цифрами в десятичную запись (араб­ скими цифрами) мы получили бы весьма простое правило: число, записанное римскими цифрами, было бы равно сумме своих цифр. Составим программу, которая запись любого данного 200


числа арабскими цифрами переводила бы в запись римскими цифрами: program римские (input, output); var п : 0..3999; (* исходное данное *) k : (М, CM, D, CD, С, XC, L, XL, X, IX, V, IV, I); s ; in teg er; (* арабское число, соответствующее *) (* римской цифре, k *) begin re ad (п); for k : — M to I do begin case k of M : s : = 1000;

C M :s : = 900; D : s : = 50 0; C D :s : = 40 0; C : s : = 10 0; X C: s : = 9 0 ; L :s : = 50; X L :s : = 40; X :s : = 10; IX: s : = 9 ; V :s : = 5; I V :s : = 4 ; I:s : = 1 end; while n — s ^ 0 do begin n : = n — s; w rite (k :\ ) end end end. Приведем другую, более сложную, но интересную прог­ рамму. В ней используются только основные римские цифры. Процедура рим записывает фрагмент данного числа п при помощи букв х, у, z в соответствии с правилами записи чисел римскими цифрами. Мы оставляем читателю самому удостовериться, что эта программа эквивалентна предыду­ щей: program римские (input, output); var n:0..3999; procedure рим (s'.in teg e r; x, у, z'.ch ar); begin while n ^ \ 0 * s do begin 201


write(x);

n : = n — 10*s end; if n ^ 9 * s th e n b e g in

w rite (z, x); n : = n — 9*s end; if n ^ 5 * s th e n b e g in

w rite [y)\ n : = n — 5*s end; if n ^ 4 * s th e n b egin

w rite (z, y); n : = n — 4*s en d end; b egin (* римские *) re ad (я); р и м ( 1 0 0 , ’M ’, D ’, ’C ’); рим (1 0 , C ’, ’L ’, ’X ’);

1,

V, T);

р и м ( ’X ’, w h i le n > l d o b egin

w rite (’ Г); n; =n — 1 en d end.

© Составьте программу, которая переводила бы запись числа римскими цифрами в десятичную запись (арабскими цифрами).

85. Почтовые индексы Посылая письмо, в указанном месте на конверте мы пи­ шем индекс. К аж дое отделение связи имеет свой индекс, не совпадающий с индексами других отделений. Поэтому письма м ож ет сортировать автомат. Он прочитывает цифры и устан авли вает, к уд а послано письмо. Предположим, что автом ат просматривает линии, образующие цифры в таком порядке, к а к показано на рисунке 32. Затем он передает в ЭВМ номера проведенных линий. Например, при прочтении единицы в ЭВМ попадают числа 4, 5, 9. Составим функцию, которая бы в соответствии с обнару­ женными автоматом проведенными линиями устанавливала бы цифру. Иными словами, исходными данными функции должны 202


быть образующие цифру линии (номера линий), а результа­ том — опознанная цифра. © Тип образа цифры на кон­ 9 верте определим так: © type индекс = set of 1..9 © Опознанная цифра будет • g принадлежать интервалу 0..9; если комбинация проведенных линий не образует никакой циф­ ры, тогда условимся считать 9 • • » О• § » значение функции равным — 1. 8 Следовательно, функция может принимать значения следую­ щего типа: цифра = — 1 ... 9 « Составим функцию: • 7 function f (х : индекс ) : цифра; begin if х = [ 1..6] then /: = 0 else if x = [4, 5, 9] then f : = 1 • • • • • • • о else if x = [3, 4, 6, 7] then f = 2 6 else if x = [3, 7, 8, 9] then / = 3 Рис. 3 2. Т р аф арет индекса почты. else if x = { 2 , 4, 5, 8] then f = 4 else if x = [2, 3, 5, 6, 8] then f : = 5 else if x = [\, 5, 6, 8, 9] then f : = 6 else if 1, 3, 9] then f : = 7 else if jc = [1..6, 8| then /: = 8 else if x = [2 , 3, 4, 7, 8] then f : = 9 else /= — 1 (* неопознанная цифра *) SgOT?»Off end ' ©

© Когда-мы пишем индексы науконверте, мы можем оши­ биться: провести ненужные линии !илн не провести их там, где необходимо. Предположим, что мы можем сделать только одну ошибку: провести одну лишнюю черту или не провести одну нужную черту (но не- обе ошибки одновременно). Посмотрим, в каких случаях можно обнаружить или даж е исправить ошибку. 1. Коды двух цифр отличаются одной позицией, наприме коды цифр 0 и 8 — только одной чертой. Если сделана ошибка как раз в этой позиции (при написании нуля прове­ дена 8-я линия или при написании восьми эта линия не проведена) , получается правильная, но не та цифра. 203


2. Коды двух цифр различаются двумя позициями. В таком случае, сделав одну ошибку, мы не сможем превратить одну цифру в другую. Если коды различаются смежными позициями, мы не сможем сказать, от какой цифры мы попали в эту позицию, т. е. не сможем исправить ошибку. 3. Коды двух цифр, например цифр 4 и 5, различаются тремя позициями. Тогда, если сделана одна ошибка, новый код отличается от правильной цифры одной позицией, а от другой цифры — двумя позициями. В таком случае можно исправить ошибку. Следовательно, для того чтобы можно было исправить одну ошибку в цифре индекса, любые две цифры кода должны различаться не менее чем тремя позициями. © Измените кодирование цифр индекса, но так, чтобы можно было исправить одну ошибку (не меняя при этом 9-линейный трафарет). Составьте функцию, опознающую ко­ ды образуемых цифр и исправляющую одну ошибку.

86. А зб ук а Морзе Составим программу, которая кодировала бы текст, пере­ даваемый русскими буквами, посредством азбуки Морзе. Между соседними символами азбуки Морзе надо оставлять по одному пробелу, а между соседними словами — допол­ нительно столько пробелов, сколько их было в переданном тексте. program морзе (input, output ); var c:char\ begin while not eof do begin read (c)\ if с in [ А .. Я ] then cas с of A : w rite( .— ); ’Б’: w rite ( —..’); В : w rite( .------’); ’T’ :w r i t e ( ------ .’); Д : w rite( - . . ’); ’E : w r it e ( .’)\ Ж :w r ite ( . . . —’); ’3 ’ :w r i t e ( ------..’); И : w rite( .. ); Й :w rite ( — ); К :w rite ( —.—’); ’Л ':w r it e ( . —..’); M :w rite( —— ); H : w rite ( —.); П’ : w rite ( .— O’ : w rite( ’P ’ :w r i t e ( . —.); С :w r ite ( ...’); 204


end.

Т : w rite ( —’), ’У : w rite ( ..— ); ф’ : w rite ( ..— X : w r ite ( Ц’ : w r it e ( — Ч’ : w r iie ( --------Ill’ : w r it e ( ------------ ’); ’Щ’ : w r it e ( ------ .—’); Ь , Ъ :w r ite (' — .—J ; Ы \w rite {^— — ); Э’ : write(^..‘— Ю : w r it e ( .. ------); ’Я’ : w rite ( .— ) end (* case *) else if c = ' then w rite ( ) else (* этого символа нет *) (* в русском алфавите *) w rite( —...— ); w r ite ( ’) end

© Составьте программу, которая расшифровывала бы текст, записанный азбукой Морзе.

87. Шифр Цезаря ЭВМ очень подходит для зашифровки и расшифровки тайнописи. Есть много способов шифровки. Один из самых простых — шифр Цезаря. Его суть — скачок через две бук­ вы: i-я буква алфавита заменяется на (г + 2)-ю букву (пред­ последняя буква алфавита заменяется на первую, а послед­ няя — на вторую букву алфавита). Например, русская посло­ вица КОНЧИЛ ДЕЛО, ГУЛЯЙ СМЕЛО была бы зашифрована следующим образом: МРПЩКН ЕЖНР ЕХНБЛ УОЖНР Составим программу зашифровки русского текста кодом Цезаря. Знаки препинания и другие символы шифровать не требуется. Чтобы найти шифр буквы с, следует выполнить следую­ щий оператор: if с = ’Я ’ then х : = ’Б else if с = 'Ю ’ then х : = ’А’ else х : = (s u c c (с)) Удостоверьтесь, что то же самое можно осуществить при помощи одного оператора присваивания: x := c h r ((o rd (c ) — o r d ( А’) + 2) mod n-\-ord (’A’)), 205


где п — число букв в алфавите. Последний оператор более универсален: написав вместо константы 2 какую-либо другую константу, мы получим новый шифр. Законченная программа выглядела бы так: program шифр (input, output)', const п = 32; var x :c h a r ; begin while not eof do begin read (x)\ if x in [ А ..’Я ] then write (chr((ord (x) — ord (’A’) + 2) mod n + o r d ( A ’))) else w rite (x)-, if eoln then w riteln end end. Последний оператор включен в цикл для того, чтобы программа располагала шифрованным текстом в таких же строках, что и исходный текст. Эта программа подходит для шифровки текста только на тех языках, алфавит которых совпадает с алфавитом ЭВМ, выполняющей программу. © Составьте программу расшифровки текста, зашифро­ ванного при помощи кода Цезаря. 88. Шифр Гронсфельда Подобен шифру Цезаря, но куда более сложен шифр Гронсфельда. Его ключ — пятизначное число. Буквы текста разбиваются на группы по пять (цифры и специальные символы не будем шифровать). Первая группа каждой цифры шифруется по способу шифровки Цезаря с ключом, роль которого играет первая цифра пятизначного числа (через столько букв делается скачок), вторая — с ключом, равным второй цифре пятизначного числа, и т. д. Таким образом, можно сказать, что поочередно используется пять ключей — цифры данного пятизначного числа. Текст может быть зашифрован шифром Гронсфельда 100 ООО способами, поэтому методом проб найти ключ очень трудно. Поскольку ключ — пятизначное число, а нам нужны толь­ ко цифры этого числа, то цифры ключа удобнее всего хранить в массиве: 206


var

к л ю ч : a rra y [0..4] of 0..9

Составим схему программы: program гронсфельд (input, output ); type цифра — 0..9; var кл ю ч : a rr a y [0..4] of цифра-, x :in te g e r ; (* данный ключ — пятизначное *) (* число *) c :c h a r ; function шифр (с : c h a r; k : циф ра): c h a r ; зашифровка данной буквы ключом k (k — цифра пятизначного числа) begin (* гронсфельд *) read(x); помещение цифр, образующих ключ х, в массиве ключ while not eof do begin read (c); если символ с — буква русского алфавита, она шифруется и печатается; в противном случае печатается сам символ if eotn then w riteln (* чтобы текст был *) (* напечатан так же, *) (* как и исходный *) end end. Функцию шифр составить несложно. Написав ее и по­ местив цифры указанного пятизначного числа в массив ключ, а такж е запрограммировав действия, названные в третьем прямоугольнике, получим законченную программу: program гронсфельд (input., output ); const пп — 3‘2 ; (* число букв в. алфавите*) 207


type цифра = 0..9; var клю ч : a rr a y [0..4] of цифра; x, (* данный ключ — пятизначное число *) i : in te g e r; с : ch ar; function шифр ( с : ch ar; k : ц иф ра):char; (* зашифровка данной буквы ключом k *) var i:in te g e r ; begin i : = o r d (c)- \-k; if iZ^znn + ord (’A’) then шифр: = c h r (i — nn) else шифр: = clir (i) end; (* шифр *) begin (* гронсфельд *) read (x): for <: = 4 downto 0 do begin • tg ключ [/j: —x mod 10; x : = x div 10 end; i: = 0; while not eof do begin read (c);f , , , : if с in [ A .. Я ] then begin 1шгИе(шифр(с, ключ [t mod 5])); i; — -i 1 end else w rite(c); if eoln then w riteln end end. © Исходные данные — два длинных текста на русском языке. Один из них зашифрован кодом Гронсфельда. Сос­ тавьте программу, которая в соответствии с частотой повто­ рений букв (см. задачу 74) в незашифрованном тексте установила бы ключ шифра Гронсфельда.

89. Король Исходные данные — координаты х, у шахматного поля, на котором стоит король. Координаты обозначаются так, как принято в шахматах; х — буквами а , b , с, ..., h, а у — цифрами от 1 до 8. Создадим программу, которая печатала 208


бы все поля, на которые может пойти король. Например, если король стоит на поле а 1, то ЭВМ должна напечатать А2 В1 В2 Эта задача очень простая. Хотя ее несложно решить без помощи ЭВМ, все же продемонстрируем, какую красивую программу можно составить для решения этой задачи. Сперва попытайтесь составить ее самостоятельно, а затем сравните с приведенной здесь. Похожую ли программу вы составили? Или, быть может, еще более совершенную? program король (input, output); type х х = ( а а а , а , Ь, с, d, е, /, g , h, hhh); у у = 0..9; \ : var x ’. a .J i ; п у : 1-8; i:xx\ '■>.

i'-yy;

:Ьпэ

begin read (лс, у)', for i : —pred (x) to succ (x) do for /: = y — 1 to y + 1 do if (i in [a..h]) and (/' in [1..8]) and ((1ф х) or (j Ф у )) Л; then w riteln (i , j : 1) end. Мы ввели в программу тип координат хх и уу, посредством которых оп­ ределили доску больших размеров (рис. 33). Она необходима, чтобы мы могли решать задачу одним и тем ж е методом, не принимая во внимание, где стоит король: на сере­ дине или на краю доски. Лишь позже, печатая ре­ зультаты, мы отбрасываем те поля, которые находят­ ся за пределами стандарт­ ной шахматной доски.

Начало а

Конец

Рис. 3 3 . Р асш и рен н ая ш ах м а тн а я л е н ­ та д л я н ахож дения ходов к ор о ля.

209


© Составьте аналогичную программу, которая печата­ ла бы все поля, на которые может пойти конь (исходные данные — координаты шахматного поля, на котором стоит конь).

90. Ханойские башни Эту задачу, иллюстрирующую рекурсию, очень любят программисты. Ее можно найти едва ли не в каждой книге по программированию. Вкратце и мы опишем ее. Эта задача связана с легендой. Рассказывают, что когда-то в Ханое стоял храм и рядом с ним — три башни (столба). На первую башню надеты 64 диска различного диаметра: самый боль­ шой диск — внизу, а самый маленький — вверху. Монахи этого храма должны были переместить все диски с первого столба на третий, соблюдая следующие правила: 1) можно перемещать лишь по одному диску; 2) больший диск нельзя класть на меньший; 3) снятый диск нельзя отложить — его необходимо сразу же надеть на другой столб. Когда дисков много (64), решение этой задачи оказы­ вается необычайно долгим. (Легенда утверждает: когда мо­ нахи закончат свою работу — а они перемещают диск за одну секунду! — наступит конец света. И в настоящее время они работают и еще долго будут работать...) Поэтому эту задачу (как игру) решают с меньшим количеством дисков. Желая составить программу, будем рассуждать так. Предположим, с первого столба (А) надо перенести на третий (С) п дисков (рис. 34). Диски пронумерованы в порядке возрастания их диаметров.

■ ■ t ■

4

В

с

Рис. 34.

Предположим, что мы умеем переносить п — 1 дисков. В этом случае п дисков перенесем посредством следующих шагов: 210


1) верхние п — 1 дисков перенесем с первого на второй, пользуясь свободным третьим столбом; 2) последний п -й диск наденем на третий столб; 3) п — 1 дисков перенесем на третий, пользуясь сво­ бодным первым столбом. Аналогичным образом можно перенести п — 1, п —2 и т. д. дисков. Когда п = I, осуществить перенос очень просто; непосредственно с первого столба на третий. Таким образом, задача решается рекурсивно. program ханой (output); type баш ня = char; v ar а, b, с'.баш ня; procedure h ( п :in teg er; а, b, с:б аш н я); procedure надеть (х, у . баш ня); begin w r i t e l n ( x , ------ > , у) V" end; begin if n > 0 then begin h(n — 1, a, c, b); н адеть(а, с); h(n — 1, b, a, c) end end; be£in a : = A ; b : — В ; с: = С ; 7z(4, a, b, c) end. а — >в В программе столбы обозначены буквами: А— >С А — первый, столб, В — второй столб и С — В— >С третий столб. На рисунке 35 приведены резуль­ А-->В С— >А таты программы для случгая, когда число дис­ с —>в ков /?=4. А— >В © Другой интересный и очень простой спо­ А—>С соб решения задачи о ханойских башнях со­ В— >С стоит в следующем. В — >А С—>А Диски нумеруются начиная с 1 (наимень­ В—>с ший) и до N (самый большой), а столбы А—> В устанавливаются в круг. При перемещении дис­ А—>С ков следует придерживаться следующих пра­ В—>С вил: 1. Диски, номера которых нечетны, можноРис. 35. Найденный перемещать только по часовой стрелке, а диски, ЭВМ п о р я­ номера которых четны,— только против часо­ д о к переноса дисков. вой стрелки. 211


2. Один и тот же диск нельзя перемещать два раза подряд. 3. Больший диск нельзя класть на меньший. Составьте программу решения задачи о ханойских баш­ нях в соответствии с этими правилами.

91. Игра «Ж изнь» Эта игра создана в 1970 г., ее автор — английский ма­ тематик Д ж . Конвей (Conway). Игру «Жизнь» достаточно исчерпывающе, приведя множество примеров, описал М. Гарднер [11]. Поэтому мы не будем пространно говорить о ней — только определим правила игры и в соответствии с ними составим программу игры для ЭВМ. В этой игре партнер не нужен — в нее можно играть одному. П равила игры. Вообразите бесконечное поле, разделенное на клетки. На каждой клетке поля ж ивет, р ож дается или погибает животное. Это зависит от условий среды, т. е. от того, сколько соседей у него на ближайших восьми клетках (четырех по сторонам и четырех по углам ). Действуют три правила существования животных: 1. Каждое животное, у которого два или три соседа, ж ивет и сохраняется до следующего поколения. 2. Животное погибает, если у него более нежели три соседа (от недостатка места), совсем нет соседей или только один сосед (от одиночества). 3. Когда рядом с какой-нибудь клеткой есть три живот­ ных (соседа), на этой клетке р ож дается новое животное. 4. Важно понять, что животные погибают и рождаются одновременно. Они образуют одно поколение. За один ход в игре в соответствии с упомянутыми правилами осущест­ вляется переход от одного поколения к другому. Д ж . Конвей рекомендует следующий способ осуществле­ ния ходов (при наличии клетчатой доски и косточек двух цветов): 1) начать с желаемой конфигурации (колонии живот­ ных), состоящей из черных косточек; 2) найти все косточки, которые должны «погибнуть», и на каждую из них надеть по одной черной косточке; 3) найти все свободные клетки, на которых должно «родиться» животное, и положить на них по одной белой косточке; 4) удалить с доски всех «погибших» животных (т. е. столбики из двух косточек), а «новорожденных» (белые косточки) заменить черными. 212


Выполнив эти операции, т. е. после первого хода, по­ лучим второе поколение. Ана­ логичным образом происхо­ дят и все остальные ходы в игре. Так получаются все новые поколения. Составим программу, ко­ торая вводила бы исходную конфигурацию (состояние) и печатала бы k поколений — этапов эволюции. В программе нельзя определить бесконечно большое поле. Должно хватать поля некоторой известной величины т Х п . Что делать, если эволюция достигает границ поля? Один из возможных выходов — прервать эволюцию. Однако эволюция могла бы продолжаться, если бы мы устранили границы поля: соединили бы любые два противоположных края поля, затем — концы полученного цилиндра. Полу­ ченная фигура, имеющая форму бублика, называется тор (рис. 36). Составим программу осуществления эволюции животных на поле, имеющем форму тора. Поле определим как тип массива: type поле = a rra y [[..lh , 1.. //] of boolean, где //, lh — измерения поля (до того, как поле превратили в тор ). Составим схему программы: program жизнь (input, output)-, const //= 10; lh = 1 0 ; (* размер поля*) type п о л е = a rr a y [1 ..lh, 1..//j of boolean-, var наст, буд'.поле; &,-(* исходное данное — число поколений *) i : in teger; procedure ввод (var I: поле); ■

К*; ■ ■ k ‘ !i >к И л

~ ■

. TO!

>!-'

"и

ввод исходного расположения животных — исходного состояния procedure дальш е (н аст:п ол е; var буд'.поле); (* появление на поле буд нового поколения *) (* животных, находящихся на поле наст *) var х, у '.in teger; (* координаты клетки поля *) 213


function соседи (н аст:п оле; х , у ‘. in te g e r): in teger; сколько соседей имеет животное, находящееся в клетке (х, у) function вы ж и вет (н аст: поле; х, у ’. in te g e r): boolean; остается ли живым животное в клетке (х, у) function родится {наст: поле; х, у ’. in te g e r): boolean; рождается ли новое животное в клетке (х, у) begin (* дальш е *) for х : = 1 to lh do for у : — 1 to // do if наст\х, y\ then буд[х, y } := вы ж ивет (наст, x, у) else буд[х , y\: = родится (наст, х , у) end; (* дальш е *) • procedure печать (1:поле); печать одного поколения — одного этапа эволюции procedure линия; печать горизонтальной линии, отделяющей одно поколение от другого begin (* жизнь *) readln(k); ввод(наст); (* печать исходного поля *) линия; печать(наст); линия; for i : — 1 to k do


begin дальш е(наст, буду, н а с т : = буд; печать(наст); линия end end. Чтобы определить число соседей у какой-нибудь клетки, следует просмотреть все находящиеся рядом клетки (слева, справа, сверху, снизу). Однако выбранная клетка может находиться на краю поля. В этом случае и требуется «скле­ ить» края — получить тор. Это осуществляется функцией соседи. Все прочие функции и процедуры просты, поэтому мы приводим законченную программу: program жизнь (input, output)-, const //= 10; lh — 10; (* размер поля*) type поле — arra y [l..//z, 1..//] of boolean ; var наст, б уд :п о л е; k, (* исходное данное — число поколений *) i : in teg er; procedure ввод (var 1:поле); (* ввод исходного состояния *) var с : char-, х, у -.integer-, (* координаты клетки поля*) begin for х : = 1 to lh do begin for у : = 1 to // do begin read(c); I [x, у ]: = с ф end; re a d In end end; (* ввод *) procedure дальш е (н аст:п оле; var буд'.поле); (* появление на поле буд животных нового поколения, *) (* находящихся на поле н аст *) var х, у '.in teger; (* координаты клетки поля*) function соседи (наст'.поле; х, у -.in teg er):in teg er; (* сколько соседей у животного в клетке (х, у ) * ) var х га, уп, га, п, s-.in teger; begin 215


s : =0; for m : = — 1 to 1 do for n : = — 1 to 1 do if not ((m — 0) and (n = 0)) then begin (* моделирование поверхности тора *) (* для этого верхний край поля *) (* соединяем с нижним, а *) (* левый край — с правым *) х т : = х - \ - т ; у п : = у -\-п; if хт< С 1 then x m := l h ; if х т > lh then х т : = 1; if y n < .l then yn := = ll; if yn >■ 11 then y n : — 1; if наст [xm, yn] then s : = s + l end; j соседи: = s end; (* соседи *) function вы ж и вет (н аст:п оле; x, у '.in teger): boolean; (* выживет ли животное в клетке (х, у) *) var s '.in te g e r; begin (* вы ж и вет *) s : = соседи (наст, х, у); вы ж и вет: = ( s — 2) or (s = 3) end; (* вы ж и вет *) function родится (н аст:п оле; х , у '.in teger): boolean; (* родится ли животное в клетке (х, у) *) b e g in

родится: = соседи(наст, х, у) = 3 e n d ; (* родится *) b e g in (* дальш е *) fo r х : = 1 to lh do fo r у : = 1 to 11 do if наст\х , у] th e n буд[х, у}: = вы ж ивет(наст, х, у) e ls e буфе, у}: = р оди тся(н аст, х, у)

end; (* дальш е *) procedure печать(1:поле); (*. печать одного поколения *) v a r х , у '.integer; b e g in fo r х : = 1 to lh do b egin

w rite( I ); fo r y : = 1 to 11 do 216


Когда исходное состояние было

ООО ОО ООО и k = 5, то ЭВМ, выполняя программу ж и зн ь, напечатала приведенные на рисунке 37 состоя­ ния — этапы эволюции. Приведенную программу жизнь измените таким образом, чтобы печать прекращ алась, к ак только эволюция останавливалась или по­ гибали все животные.

92. Игра в спички Уже давно ЭВМ «обучается различным иг­ рам»: создаются программы по ш ахматам , шашкам и другим играм.

I I I I I I I I I I

0 ООО О 0 0 ООО ООО ООО ООО 0

I I I I 3 I 1

I I I I I I I I I I I I I I I I I I I I I I I I I I I I I

0 0 0

0 ООО 00 0 0 ООО 0

I I I I I

0 0 0

I I

I I I I г

1 1 I 1 I I I I I I I I I I I

ООО . I 0 1 0 1 0 1 ООО I

с

end end; (* печать *) procedure линия; (* горизонтальная линия *) var i'.in te g e r; begin for i : = 0 to //+ 1 do w rite ( — ); w riteln end; (* линия *) begin (* ж изнь *) readln(k); ввод(наст); (* печать исходного поля *) линия; печать(наст ); линия; for г : = 1 to k do begin дальш е(наст, буд); н а с т := б у д ; печать(наст ); линия . end end.

о

if l[x, у] then w rite( О ) else w rite( ); w r ite ln (l)

0 0 0

I I

ООО 0 0 ООО

I I I

ООО 0 0 0

I 0 I 0 1 0 1

ООО

I

Рис. 37. Н ачальн ая ф игура и пять п о к о ле­ ний ее э во лю ­ ции. 217


Программы игр стали особенно популярны с появлением персональных компьютеров. Ведь каждому приятно иметь не капризного и никогда не устающего партнера. Игры различаются правилами, степенью сложности. Однако все они имеют много общего. Играющие по очереди делают ходы. Каждый играющий старается из всех воз­ можных ходов выбрать самый лучший, т. е. такой, который обеспечивает победу. Если игра простая, то ее можно пол­ ностью проанализировать и наметить лучший ход в любой ситуации. Одна из самых популярных простых игр — это игра в спички (палочки). Игроки поочередно берут спички из кучки. За один ход можно взять одну или две спички. Проигры­ вает тот, кто берет последнюю спичку. Таким образом, в данной игре ситуация — это количество спичек, находящих­ ся в кучке перед ходом, а ход — количество берущихся спичек (одна или две). Составим функцию, исходным данным (параметром) ко­ торой была бы ситуация (количество спичек в кучке), а результатом— ход (число берущихся спичек). Можно подразделить все ситуации на выигрышные и проигрышные. Когда в кучке лежит одна спичка, создается проигрышная ситуация. Когда в кучке — две спички, создает­ ся выигрышная ситуация, так как, взяв одну спичку, созда­ дим для соперника проигрышную ситуацию. Последующая ситуация (три спички в кучке) такж е является выигрышной, так как, взяв две спички, мы оставим сопернику одну. Проигрышная ситуация создается, когда в кучке будет четыре спички, так как, взяв одну или две спички, мы «по­ дарим» сопернику выигрышную ситуацию. Р ассуж дая далее аналогичным образом, мы получим ряд следующих ситуа­ ций (выигрышные ситуации отмечены плюсами, проигрыш­ ные — минусами): 1 2 3 4 5 6 7 8 9 10 11 ... — + + — + + — + + — + Таким образом, выигрывает игру тот, кто может оставить сопернику кучки, состоящие соответственно (считая с «кон­ ца») из 1, 4, 7, 10, 13, 16 спичек и т. д. вплоть до числа, близко­ го к числу спичек в кучке. Несложно установить законо­ мерность. Теперь остается только записать функцию (перед этим определим типы данных): type cuTyau,uH = Q..maxint\ ход = 1..2; function взять ( s : си туац и я):хо д; begin 218


if s mod 3 = 0 then else

взять: = 2 взять : = 1

end Когда создалась проигрышная ситуация, берется одна спичка («Если проиграю, то хоть не так скоро...»). © Составьте функцию, позволяющую определять ходы в обобщенной игре в спички (когда за один раз разрешается брать от 1 до р спичек, причем р намного меньше, чем число спичек в куч ке). ©.© Кто первый скажет сто? Играют двое. Игроки по очереди говорят по числу из интервала [1; 10]. Все ска­ занные числа складываются. Игра продолжается до тех пор, пока окончательная сумма не станет равна 100. Выигрывает тот, кто первый произнесет эту сумму. Составьте функцию, позволяющую определять ходы в этой игре.

93. Судья соревнований Играть могут не только человек на ЭВМ, но и две ЭВМ или две программы одной и той же машины между собой. Игра между двумя программами одной ЭВМ выявляет к а ­ чество программ и работу их авторов. С 1974 г. органи­ зуются даж е международные турниры по выявлению лучших шахматных программ. Соревнования проводит судья. Когда игроки— про­ граммы, то, естественно, и судит программа. Составим программу, которая проводила бы соревнова­ ния между двумя функциям# игры в спички. program судья {input, output); const m ax = 20; (* максимальное число спичек *) type ход = 1 ..2 ; (* сколько берется спичек*) ситуация = 0 ..m ax; var s : ситуация; j : ход; кто : 1..2; (* кто берет спичку *) член, (* глобальная переменная функции pseudo *) k, х, z : in teg er; function p seu d o '.in teger; (* псевдослучайные натуральные числа *) ... (см. задачу 56) function первый (s : си туац и я):ход; (* ход первого игрока *) begin 219


if s mod 3 = 0 then else

первый'. = 2 п ер вы й : = 1

end; function второй ( s : си туац и я):xod\

■(* ход второго игрока *) begin второй: = pseudo mod 2 + 1 end; begin (* судья *) член: = 2 0 ООО; (* любое натуральное число *)

(* перестановка перед метанием жребия *) read(x); for k : — 1 to x do 2 ; = pseudo; (* кидаем жребий *) s : = pseudo mod m ax-\ - 1; («сколько спичек и*) кто : = p se u d o mod 2 + 1 ; (* кто начинает *) шгйе//1(’ПРОТОКОЛ ИГРЫ В СПИЧКИ’); while s > 0 do begin if к то — 1 then j : = первый (s) (* берет первый*) else /; = второй (s); (* берет второй *.) w riteln (s ,—, /: 1, (’, кто : I, ’) — , s — /: 1); s : = s — j ; (* сколько осталось*) кто : = кто mod 2 + 1 (* кому брать *)' end; w rite( ’ В Ы И Г РАЛ ’); if к то — 1 then w rite ln ( ПЕРВЫЙ) else w rite ln ( ВТОРОЙ’) end. Как и в любом соревновании, прежде всего бросается жребий — выясняется, сколько спичек в кучке и-кто делает первый ход. Далее по очереди действуют две игровые функ­ ции — первый и второй: устанавливается, кому брать, сколь­ ко спичек осталось в куЧке и, наконец, кто выиграл. Печа­ тается и протокол игры: количество спичек, сколько и (в скобках) кто брал, сколько спичек оставалось. В этой программе функция первого игрока первый вы­ бирает абсолютно лучшие ходы, а второй игрок еще не осмыслил игру, поэтому его функция делает случайные ходы. Ясно, что функция первого играющего не пропускает оши­ бок, допущенных вторым, и выигрывает чаще. Результаты, напечатанные программой судья, могут выглядеть так: 220


ПРОТОКОЛ ИГРЫ в с п и ч к и 10— 1 (1) = 9 9 — 2 (2) = 7 7 — 1 (1) = 6 6 — 1 (2) = 5 5 — 1 (1) = 4 4 — 2 (2) = 2 2 — 1(1 )= 1 1— 1 ( 2 )= 0 ВЫИГРАЛ ПЕРВЫ Й © Измените программу судья таким образом, чтобы она позволяла функциям сыграть п раз (п — исходное д а н ­ ное) и печатала бы только конечный результат соревно­ вания, выраженный в виде соотношения (и не п ечатала бы п р ото кол ).

94. Лабиринт Во многих книгах, ж у р н ал ах и газетах приводятся го­ ловоломки, интересные тем, что для их решения не тре­ буется особых математических или каких-либо других спе­ циальных научных знаний. К таким зад а ч а м относятся поиск пути в сложном лабиринте, чтение текста ходом ш а х­ матного коня, расстановка фигур на шахматной доске т а ­ ким образом, чтобы удовлетворить известным условиям. Такие задачи можно решить, научившись систематично р ас­ сматривать и оценивать все возможные варианты решения задачи. В этом большую помощь может о казать ЭВМ. Приведем достаточно универсальный метод решения го­ ловоломок, который часто называю т методом проб. П роил­ люстрируем его на примере. На 38 рисунке приведен очень простой лабиринт. П опадаю т в него в пункте А. Следует найти путь из пункта А до единственного выхода, распо­ ложенного в другом конце лабиринта (он обозначен бук­ вой Е ). Находясь в точке А, мы не знаем, какой выход открыт и как к нему пройти. Поэтому на каждом из «пе­ рекрестков» лабиринта нужно выбирать одну из двух дорог. Д оговоримся сн ач ал а испытывать левую дорогу. И з пункта А мы тогда попадаем в пункт В, из В — в С, из С — в D. Отсюда дороги нет. Мы снова в озвращ аем ся на б лиж айш ий «пе­ рекресток» С и из него пробуем пройти по новой (правой) дороге в пункт Е. Здесь мы обнаруж иваем выход. З а д а ч а решена. Ее решение — путь АВСЕ. 221


ммммммммммм Рис. 38. Пример л аб ирин та. м м ММ м м м мм М М МММ мм МММ м ММ м мммм Рис. 39. Все воз­ мм мммммммм мож ны е пути л а ­ мм мм биринта ( а ) из М М МММ мм центра на окра ину (б ). мммм м м мммм мм Обратим внимание на то, что, узнав, что мы а) ммммммммммм пошли неверным путем, мы не начинаем решать М М+++ зад ач у заново, а делаем лиш ь один ш аг н азад ММ м м м+мм и снова пытаемся идти с этого места. Если, сде­ м м ммм+мм лав шаг назад, обнаруж иваем, что нет новых М М М+++ м неисследованных путей, д елаем н а за д еще один ММ М ++ММММ шаг и т. д. до тех пор, пока мы не доходим до мм мммммммм места, из которого можно пойти новым, немм мм исдробованным путем. Если, возвращ аясь, мы м м МММ мм дошли до исходной точки и из нее нет новых мммм м путей, то за д а ч а не имеет решения. м мммм мм Приведем несколько за д а ч (помимо д а н ­ ной, так ж е см. задачи 95, 96 и 97), которые ммммммммммм решаются методом проб. Решим зад ач у о л а ­ м +++ м биринте. мм+м+м м м Поле состоит из нечетного числа клеток. M +М+МММ мм Одни клетки черные, другие — бе'лые, централь­ м+м+м м ная клетка д олж н а быть белой. Черные клетки ММ+М++ мммм обозначим посредством символа М, белые — мм+мммммммм символом пробела (рис. 39, а ) . ММ+++ мм Требуется найти все пути из центральной м м+ммм мм клетки на границу доски. Идти можно только ММММ+++++ м м мммм+мм по белым клеткам по горизонтали или по верти­ кали. По диагонали идти нельзя. ммммммммммм Составим программу решения этой задачи. м +++ м Напечатаем столько лабиринтов, сколько имеет­ мм+м+м м м ся различных путей. Путь будем помечать сим­ М +М+МММ мм волом ’ + ’ (на рисунке 3 9 , 6 приведены все м+м+м м пути из рассматриваемого л аб и р и н та). ММ+М++ мммм Условимся, что положение клетки л аб и ри н ­ мм+мммммммм та на поле определяется координатами (х, у), ММ+++++++ММ где х = 1 , 2, ..., п; у = 1, 2, •••, п (размер по­ М М МММ+ММ ля — п*п клеток, причем п — нечетное ч и с л о ). мммм +м м мммм+мм Исходное положение — клетка (п div 2 + 1 , п div 2 + 1). б) 222


Схема программы выглядела бы так: p ro g r a m лабиринт (input, output)-, const п — 11; (* размер лабиринта — п*п клеток *) (* п — нечетное число *) type лабиринт — a r r a y [1 ..п, 1..га] of char-, v a r лаб:лабиринт-, proced u re идем (лаб'.лабиринт-, х, у '.integer)-, поиск путей из центра лабиринта до края — каж ды й найденный путь печатается begin ввод лабиринта идем(лаб, га div 2 + 1, ra div 2 + 1 ) (* начинаем с середины *) end. П рограм м а простая. Самое важное в ней — процедура идем, которая предназначена д ля того, чтобы найти все пути из центра лабиринта до самого края. К ак же найти хотя бы один путь? Составить такую про­ цедуру, которая сразу бы находила весь путь, очень труд­ но. Поэтому применим рекурсию. Составим процедуру, ко­ торая д ел ал а бы только один шаг, т. е. проверяла бы только одну клетку лабиринта. Перед тем как дел ать новый ход, процедура д олж на будет снова обратиться к самой себе. Такие обращ ения продолжаю тся до тех пор, пока мы не доходим до края доски или не попадаем в тупик. Если мы попали в тупик, в о звращ аем ся на один ш аг н азад и пыта­ емся идти другим путем. Из одной клетки можно идти в че­ тырех различных направлениях (влево, вправо, вниз, вверх). Установить, что мы дошли до кр ая лабиринта, можно так: if (х = \ ) or (х — п ) o r ( у = 1) or (у = п) then... (* край *) В таком случае схема процедуры идем может выглядеть так: procedure идем (var лаб:лабиринт-, х, у '.integer)-, begin if

клетка (х, у) свободна

then 223


begin ш аг на клетку (х, у) if

дошли до края лабиринта

then

печатается найденный путь

else

попытка идти по очереди во все возможные направления (испытываются все четыре возможные клетки)

ш аг испытан — делается один ш аг н азад

end end Д л я печати найденных путей в лабиринте составим про­ цедуру печать. Вся программа тогда будет выглядеть так: p ro g ra n i лабиринт {input, o u tp u t); const « = 11; (* размер лабиринта п*п клеток*) {* п — нечетное число *) type лабиринт — a r r a y [1 ..п, 1,.п\ of char-, v a r л а б : лабиринт, х, у :integer\ procedure печать { л а б ’.лабиринт)] {* печать найденного пути в лабиринте *) v ar х, у . integer-, begin for х : = 1 to n do begin for у : = 1 to n do write{Aa6 [x, y]); writeln end; writeln end; (* печать *) procedure идем {лаб: лабиринт-, x, у '.integer)-, 224


begin if л а б [x, y \ = (* если путь свободный *) then begin л а б [x, y\: (* делается ш а г * ) if (x = 1) or (x = n ) or (* если оказы ваем ся *) ( у — I) or (у — n) (* на краю лабиринта *) then (* печатается найденный путь *) печать(лаб) else (* еще не край лабиринта *) begin (* попытка идти во всех возможных *) идем(лаб, х + 1 , у); (* направлениях *) идем(лаб, х, у-\- 1); идем(лаб, х — 1, у); идем(лаб, х , у — 1) end; л а б [х, у}: = (* возвращение н а з а д * ) end end; (* идем *) begin (* лабиринт *) (* ввод лабиринта *) for л:; = 1 to п do begin for у . = 1 to я do геас1(лаб [х, у]); read In end; идем(лаб, п div 2 + 1 , п div 2 + 1 ) (* начинаем с* ) en d _ (* середины *) В процедуре идем после слова else имеются четыре ре­ курсивных обращ ения, выполняя которые мы пытаемся идти всеми четырьмя направлениями. Обратим внимание на одну немаловаж ную деталь. В процедурах идем и печать массив лаб, в котором хранится лабиринт, передается параметрами-значениями, поскольку не надо восстанавливать результат: он печатается на самом глубоком уровне рекурсии. После каж дого обращ ения к этой процедуре для массива л а б выделяется новое место в. па­ мяти. Это очень неэкономно (в особенности это касается рекурсивной процедуры идем, поскольку к ней приходится о б ращ аться много раз и каж ды й раз создается новый экзем ­ пляр л аб и ри н та). Д л я сбережения памяти следовало бы или считать массив л а б глобальной переменной, или передать его посредством параметров-переменных. (П арам етрам переменным в памяти не выделяется места, а их значения & ЗаказЗДЗ

225


хранятся вместе со значениями соответствующих ф ак ти ­ ческих параметров.) Однако процедуру, в которой есть гло­ бальные переменные, намного труднее читать. Объявление параметра-переменной, когда нужно только одно значение, вводит в заб луж д ен и е того, кто читает процедуру. 0 Измените приведенную программу лабиринт таким образом, чтобы на печать выводился бы лиш ь кратчайший путь из центра лабиринта до края.

95. Обход шахматной доски ходом коня Составим программу, в соответствии с которой ш а х м ат­ ный конь мог бы обойти всю доску, побывав на каждом поле всего один раз. Исходное положение коня — на верхнем левом поле. Поля доски занумерованы так, как это показано на рисунке 40. Ходы коня занумеруем посредством чисел 1, 2, 3, ..., 64. Определим шахматную доску через тип массива: type доска — a r r a y [1 ..п, 1,.п\ of 0..-64 Эта за д а ч а реш ается описанным выше методом проб (см. зад ач у 94). Самое важ н ое — процедура, зап ол н яю щ ая все поля доски порядковыми номерами ходов. Создать процедуру, которая сразу бы реш ала зад ач у (т. е. зап олн яла бы все поля доски номерами ходов), очень трудно. Поэтому мы будем использовать процедуру, которая д ел ал а бы только один ход конем и зап ол н ял а бы только одно поле доски. О б р ащ аяс ь к ней много раз, можно за п о л ­ нить всю доску или зайти в тупик (доска ещ е не заполнена, а ходить некуда). Тогда надо вернуться и попытаться идти новым путем. Д л я этого удоб­ 1 2 Ъ U 5 6 7 8 но использовать рекурсив­ 7 ную процедуру. С одного поля у коня не 2 более 8 различных ходов. Е с­ ли конь стоит на поле (х, у), 3 то любое (одно из восьми 4 1 возможных) новое п олож е­ ние коня можно вычислить 5 следующим образом:

© © © © ©

6

© ©

©

7 <9

■0\)

Рис. 40. Восемь ходов ш ахм атного коня. 226

(x + cx[k), y + cy[k]), здесь k — порядковый номер варианта ( 1 ^ / г ^ 8 ) . З н а ч е ­ ния массивов сх и су можно установить по образцу, при­ веденному на рисунке 40.


Приведем законченную программу: p ro g r a m конь (input, o u tp u t); const п — 8;(* размер доски *) пп = 64; (* число полей пп = п*п *) type доска = a r r a y [\..п, \..п\ of 0 ../г/г; v a r дос:доска-, х, у '.integer-, (* координаты поля *) су щ : boolean-, (* существует ли решение*) сх, с у : a r r a y |1..8| of integer-, p rocedu re схсу, (* заполняем массивы сх и си*) begin cx 1 : = 2 ; cx 2 : = 1; cx 3 : — — 1; cx 4 : = — 2; cx 5 : = — 2; cx 6 : = — 1; cx 7 : = 1; cx 8 : = 2 ; end; (*схсу *) procedure идем

cy 1 cy 2 cy 3 cy 4 cy 5 cy 6 cy 7' cy 8

: = 1; : =2; : ==2; : = 1; : — — 1; : = — 2; : — — 2; := —1

(var дос:доска-, х„ у, (* положение коня *) i:integer-, (* номер хода *) v a r суш,: = boolean)-, v ar k, (* номер варианта *) и, v .’integer-, (* новое положение коня *) begin k:=0; repeat k: — k - \ - 1; и: = x + cx [k\, v: = y + cy [k\; if ( u > 1) a n d ( u ^ . n ) an d ( u ^ l ) an d (u < r c ) then (* не вышли за пределы доски *) if дос [и, у] = 0 then begin дос [и, v ]: = / ; if i < n n then begin идем(дос, и, v, i + \ , сущ); if not сущ then doc [u, yj: = 0 end else сущ : = t r u e end 227


until сущ (* заполнена вся доска *) or {k = 8) (* рассмотрены все варианты *) end; (* идем *) begin (* конь *) (* заполнение массива дос нулями *) for х : = 1 to и do for у: = 1 to п do дос [х, у]: = 0 ; схсу; сущ : — false; дос [1, 1]: = 1; идем{дос, 1, 1, 2, сущ ); (* печать результатов *) if сущ then (* печатается доска, заполненная *) (* номерами ходов *) for л:: = 1 to п do begin for у : = 1 to п do write(doc [x, у}:3); writeln end else w riteln( Р Е Ш Е Н И Я Н Е Т ’) end. Эта программа печатает результаты, представленные на рисунке 41. 0 Измените программу конь таким образом, чтобы с любого поля (его координаты будут исходными данными) конь мог обойти все поля доски.

96. Восемь ферзей М ожно ли расставить на шахматной доске восемь ф ер­ зей так, чтобы они не угрожали друг другу (иначе говоря, чтобы никакие две фигуры не стояли ни на одной горизон­ тали, ни на одной вертикали, ни на одной д и а го н а ли )? Эта зад а ч а приводится во многих книгах по занимательной мате­ матике [11], поэтому мы не будем подробно говорить о ней, а только составим программу, которая находила бы конкрет­ ное расположение ферзей. Применим описанный выше метод проб (см. зад ач у 94). Поля доски занумерованы так, как это показано на рисунке 42. Составим схему программы: 228


1 38 59 36 43 48 57 52

60 35 2 49 58 51 44 47

39 32 37 42 3 46 53 56

34 61 40 27 50 55 4 45

31 10 33 62 41 26 23 54

18 63 28 11 24 21 14 5

9 30 19 16 7 12 25 22

64 17 8 29 20 15 6 13

А Рис. 41. О тпечатанны й резуль­ та т програм мы конь — обход ко­ нем ш ахм атной ленты . Н а ч а л ь ­ ное полож ение — верхняя л ев ая клетка. Рис. 42. У становка ф е р зя в поле, помеченном точкой: надо проверить, нет ли ф ерзей под ударом в полях, через которые проходят стрелки.

p ro g r a m ферзи (o u tp u t); const п — 8; (* доска п*п клеток*) type доска = a r r a y [\..п, 1..п] of boolean; v a r дос:доска; у ж е'.boolean; procedure ставим (var дос:доска; х : integer; v a r у ж е'.boolean); по очереди просматриваются поля x -Й строки и, где возможно, ставится ферзь pro ced ure печать(дос:доска); печатается доска с расставленными на ней ферзями begin (*ферзи *) заполнение массива дос значениями false уже: = false; ставим(дос, 1, уже); (* начинаем с первой строки *) печать(дос) end. Рекурсивная процедура расстановка составлена с при­ менением метода проб (см. зад ачу 94). Составив процедуры расстановка и печать, а так ж е з а ­ 229


писав действия, заключенные в прямоугольники в схеме программы ф ер зи , мы получим законченную программу: p ro g r a m ферзи (o u tp u t); const п = 8; (* доска п*п клеток*) type доска — a r r a y \\..п, \..п } of boo lea n; v a r дос :доска-, у ж е : boolean-, х, у '.Integer-, function можно (var дос'.доска-, х, у '.integer): boolean-, (* можно ли поставить ферзя на поле (х, у) *) v a r i, j'.integer; за н : boolean; begin (* можно *) i : = x ; за н : = false; while ( t > l ) and not зан do (* просмотр вертикали*) begin i : — i — 1; зан: — doc [i, y] end;

i: = x ; j : = y ; (* просмотр левой диагонали *) while ( { > 1) and ( / > 1 ) and not за н do begin i : = i — 1; / : = / — !; за н: = д о с [г, /] end; i : = x ; j: = y ; (* просмотр правой диагонали *) while ( / > 1 ) an d (j< L n) an d not зан do begin i : = i — 1; / : = i + 1; з а н : = doc [i, j ] end; можно: = not зан end; (* можно *) procedure ставим (var дос'.доска; x'.integer; var уж е '.boolean); v a r у '.integer; begin (* ставим *) y : = 0; repeat

y:=y+\;


if можно(дос, x, у) then (* на поле (х, у) можно поставить ферзя *) begin дос [х, у}: = true; if х — п then уж е : = t r u e (* поставлен последний ферзь *) else begin ставим(дос, х -j- 1, уже); if not уже then дос [х, у]: = false end end until уже or (y = n ) end; (* ставим *) p rocedu re печать (дос'.доска); (* печать доски и расположенных на ней ферзей *) v a r i: integer; procedure тире (n: integer); v a r i: integer; begin write ( + ); for i : = 1 to n do write ( — ’); writeln ( + ) end; procedure строка (i: integer); v a r j:in teg er; begin write( I ); for j : = 1 to n do if doc[i, j] then write (’*’) else write writeln ( I ) end; (* строка *) begin (* печать *) тире (2*n); for i : = 1 to n do строка (i); тире (2*n); end; (* печать *) >egin (* ферзи *) (* массив дос заполняется значениями false *' for х: = 1 to п do (* v ^ o v i *; ««pad for у : — 1 to n do doc [x, y}: = false; уже: = false; 231


ставим (дос, 1, уже); печать (дос) end. Результаты этой программы представ­ . . ж лены на рисунке 43. * . . © Измените программу так, чтобы она находила все возможные способы р ас с т а ­ новки ферзей. Рис. 43. Р а с п еч а ­ © © Есть много вариантов шахматной танный резул ьтат игры: играют на досках различной формы, программы ферзи — располож ен ие вось­ добавляю т необычные фигуры и т. д. Н о­ ми ферзей на ш ах ­ вые фигуры можно получить, соединив матной доске. возможности нескольких известных фигур. Например, ам азон ка — это фигура, которая мож ет хо­ дить как ферзь и как конь. Составьте программы, которые на шахматной доске размером 1 0 X 1 0 полей расставляли бы 10 амазонок. А м а­ зонки долж ны быть расставлены так, чтобы они не угрожали друг другу. * . . *

97. Раскраска карты Территории государств, имеющих общую границу, на политической карте раскраш иваю тся различными красками. Уже в прошлом столетии было установлено, что для р а с ­ краски любой карты достаточно четырех цветов. Эту так называемую гипотезу четырех цветов выдвинул в 1852 г. английский математик и логик О. де М орган (de M o rg a n , 1806— 1871). К сожалению, он сам не смог д о каза ть это утверждение. Только в 1976 г. быстродействующая ЭВМ Иллинойского университета установила, что четырех цве­ тов достаточно д л я раскраски любой карты нашей планеты, любого участка глобуса. Составим программу, которая подбирала бы цвета (к рас­ ный, желтый, синий и зеленый) для раскраски территорий районов на карте административного деления своей области. Д л я решения этой задачи очень важ н о иметь правильно подготовленные исходные данные (информацию об общих границах у рай он ов). Условимся, что исходные данные, относящиеся к одному району, содерж атся в одной строке данных и расположены в следующем порядке: 1) номер района; 2) название района; 3) номера тех районов, с которыми рассматриваемый район имеет общую границу; конец последовательности помечается нулем. 232


Исходные данные будем хранить в одномерном массиве, каждый i-й член которого представляет собой множество соседних районов, т. е. множество районов, имеющих общую границу с районом под номером I. Таким образом, информа­ ция об общих границах районов будет находиться в следую­ щем массиве: v a r границы: a r r a y [районы ] of набрай Здесь типы районы и набрай определены так: type районы = 1..44; н а б р а й = set of районы Н а звани я районов будем хранить отдельно (они понадо­ бятся только при печати результатов) в следующем массиве: v a r им рай: a r r a y \районы\ of a r r a y 11 ..20] of char Чтобы мы знали, какими цветами раскраш ивать терри­ тории районов, мы создали массив о щ каж ды й элемент ко­ торого представляет собой множество районов, раскр аш ен ­ ных цветом соответствующего индекса. Тип цвета определяется так: type цвет = (красный, желтый, синий, зелены й ), а сам массив описывается следующим образом: v a r окр: a r r a y [цвет] of набрай Уже из формулировки задачи ясно, что ее следует решать методом проб (см. зад ач у 94). Следовательно, придется составить рекурсивную процедуру, которая каж дом у району подбирала бы цвет. Н ас интересует лиш ь один вариант раскраски: важ н о раскрасить указанными цветами карту области, а сколькими способами это можно сделать, не имеет никакого значения. Поэтому процедура д о лж н а пре­ кр а щ ать работу, как только она обнаружит хотя бы одно решение. Приведем схему процедуры: procedure окраска (г: районы; v a r окрашено: boolean); begin берется первый цвет из набора repeat этот цвет не использовался для окраски какого-либо района, граничащего с г-м районом 233


then begin этим цветом окраш ивается район i if г = 44 then (* окрашен последний район *) окрашено : = t r u e else begin окраска ( i + 1 , окрашено) if not окрашено then

этот цвет не подходит для района г

end end; берется другой цвет из набора until окрашено or end

рассмотрен весь набор цветов

Действия, заключенные в прямоугольники, весьма просты. Мы запишем их на языке П а ска л ь и включим в программу. Кроме того, составим и включим в программу процедуры ввода исходных Данных и печати результатов. Они длинно­ ваты (особенно процедура в в о д ) , но достаточно просты. Поэтому мы не будем пояснять деталей. Во всех процедурах массивы границы и окр представляют собой глобальные переменные. Приведем законченную программу: p ro g ra m карта (input, output)-, const m a x = 44; (* число районов*) type районы = 1..max; набрай — set of районы; цвет = (красный, желтый, синий, зеленый); v a r г р а н и ц ы : a r r a y [ра йоны ] of наб р ай; о к р : a r r a y [цвет] of набрай; й м раи гаггау [/жионы] of a r r a y [ 1..20] o f char; ц в : цвет; о кр а ш е н о : boolean; p rocedu re окраска (i:районы; v a r о к р а ш е н о '. boolean); v a r цв'.цвет; begin 234


ц в : — красный.-, repeat if границы \i\*OKp [цв] = [ ] then begin (* нет районов, граничивших с г- м *) (* районом, цвет которых цв *) (* район г окраш ивается цветом цв *) окр [ ц в ] : = о к р [цв]+[/]; if i = m a x then .(* окрашен последний район *) окрашено'. = t r u e else begin окраска ( г + 1 , окраш ено); if not окрашено then окр [цв]'. = окр [цв]— [г] end end; (* берется другой цвет из набора *) if цв Ф з е л е н ы й then ц в: =Бисс(цв) else ц в : — красный until окрашено or (цв — красный) end; (* окраска *) p ro c ed u re ввод; (* вводится информация о границах районов *) v a r номер, i : районы, k : integer-, с: char] begin for i: = 1 to m a x do begin read (номер); (* ввод н азвания района *) for k: = \ to 20 do begin read (c)] имрай [номер, k] : — с end; (* ввод информации о границах районов *) границы [номер] : — [ J ; read(k)] repeat границы [номер] : — границы [номер] + [/г]; read (к) until /,- 0 end 2,чв


end; (* ввод *) procedure печать; v a r i: районы; ц в : цвет; j '.integer; begin for i: = 1 to m a x do begin for j : = 1 to 20 do w rite (имрай [i, /]); ц в '. — красный; while not (i in окр [ц в ]) do цв: = зисс(цв); writelnine) end end; (* печать *) begin (* карта *) ввод; окраш ено: = false; for ц в : — красный to зелены й do о к р [ ц в } : = [ ]; окраска (7, окраш ено); печать end. Обратим внимание на то, что программа карта подходит д ля раскраски любой карты (например, политической карты Европы, карты административного деления на области и т. п .),— надо только изменить число окраш иваемы х тер­ риторий (константа m a x ).

98. Лексический анализ Одной из самых сложных и интересных за д а ч програм­ мирования является составление программ д л я транслятора (ко м п и л ято р а ). Д ли н а программ, используемых на практике трансляторов языков программирования (например, П а ска ля , P L /1 , Ф ор тран а), может быть от нескольких тысяч до нескольких десятков тысяч операторов. Д л я того чтобы составлять или разби рать такие программы, требуется много времени и сил. Однако принципы составления и действия основных т р ан сл я­ торов можно проиллюстрировать, построив очень простой язык. Мы не будем создавать нового языка, а возьмем очень небольшое подмножество (микроподмножество) язы ка Пас236


программа PROGRAM

-

имя-

-описание —

блок

описание

~ - ^ и м я ------■,- - * { : V s - INTEGER — э*С w V.

Г»- VAR

Влок -оператор-

В ЕБ IN

END

-C D оператор ■«-

■ Выражение

и м я -----------

дыратение -те р м -

терм -множитель-

X о/к

MOD

1 П

ZJZ

X

множитель — > - число■имя—► выражение —

)"

'N

|!)л

Рис. 44. Синтаксические диаграм м ы микроподмнож ества язьц, 3

асКаль.


каль, в котором есть только целые неотрицательные числа и операторы только одного вида — присваивания. Синтаксические диаграммы этого подмножества показаны на рисунке 44. В подмножестве будем употреблять только заглавны е буквы. Так написанная программа отчетливее вы­ делится из других программ на языке Паскаль. Чтобы текст программы был удобен для машины, его нужно преобразовать — разделить на лексемы. Лексема это символ, имеющий самостоятельное значение в програм­ ме: число, имя, служебное слово, символ операции. Определим лексему и массив лексем рассматриваемого подмножества язы ка П аскаль: type лексема = (плю с, минус, звездочка, лскобка, пскобка, присваивание, двоеточие, точка, запятая, точкасзапятой, program c, beginc, endc, var с, integer с, d iv e , mode, имя; число); лексемы = a r r a y [1 ..max] of лексема К словам язы ка (например, p r o g r a m ) мы прибавлялиокончание с (символ), чтобы лексем ы '(кон стан ты ) отлича­ лись от базовых слов язы ка Паскаль. С ам а я первая зад а ч а каж дого транслятора — это лекси­ ческий анализ. Д л я решения этой задачи составим процедуру лексика, предназначенную для того, чтобы прочитать текст программы, написанный на определенном нами подмноже­ стве язы ка П аскаль, и сформ ировать массив лексем. З а г о л о ­ вок процедуры долж ен был бы выглядеть так: p rocedu re лексика (var леке:лексем ы ) Например, процедуре предъявлен такой текст программы: P R O G R A M M IKRO; VAR ALFA: IN TEG ER ; B E G IN A LFA : = 2 END. Тогда процедура формирует массив лек, состоящий из следующих элементов: pro g ra m c имя точкасзапятой varc имя двоеточие integer с точкасзапятой beginc имя при сваивание число endc точка Значение переменной k долж но быть равно 14, поскольку в тексте программы содержится именно столько лексем. Составим схему процедуры: proced u re лексика (var лек :л е кс ем ы ); определения и описания 238


v a r с : char; begin repeat read(c) ; if с in [’А .-’Я ] then образование слова и запись соответствующей лексемы в массив лек else if с in [’o ’..’9 ] then образование числа и запись лексемы число в массив лек else if с = ’: ’ then установление, является ли данный символ двоеточием или началом зн ак а присваивания; запись соответствующей лексемы в массив лек

else if с in [

> 1 1 + . У,

> >> >,> 7,

then

превращение символа в лексему и запись ее в массив лек until ( с = • ’) or eof end С ам а я трудная часть — анализ слов. П реж де всего надо образовать (сформировать) слово. Это можно сделать, читая текст программы дальш е, до тех пор пока следуют буквы или цифры. Определим тип слово и переменную z, относящуюся к это­ му типу: type слово — packed a r r a y [1. 8] of char

var г '.слово Предположим, что выделенное и Тексте программы слово уже ип 1и. I■ I tiiiHH'iiiit м переменной Теперь надо про верить, (III пи I. II m • Mil I ’hitluM Я 11)1 Ки IUIII именем. Д ля а;!»


этого мы создали массив слов языка: v ar слова', a r r a y [programc.. mode] of слово В этот массив надо заранее записать слова языка: сло ва [program c]:— PRO GRAM П слова [beginc]: = ’B E G IN □ □ □ ’ слова [mode]: = ’M OD □ □ □ □ □ ’ Здесь и далее в тексте знаком □ мы будем обозначать символ пробела (чтобы легче было подсчитать требующиеся пробелы), так как при предъявлении программы ЭВМ на этом месте долж ны быть настоящие пробелы (пустое место). Ж е л а я установить, к а к а я лексема соответствует данному слову, надо найти его в массиве слова. Индекс элемента, который совпадет с искомым словом, и будет искомая лексема. Если такого слова в массиве нет, то, значит, это слово — имя переменной. Все прочие части процедуры более простые. С разу вклю­ чим их в программу: procedure л екси ка (var лек: лексемы)-, type слово = packed a r r a y [1.8] of char-, v a r z : слово-, (* анализируемое слово*) с л о в а : a r r a y [programc..mode] of слово-, л к : лексема-, г : 0..8; (* фактическое число букв в слове*) с: char; (* анализируемый символ *) /г: 0..max; procedure новая (х: лексема)-, (* запись лексемы х в массив л ек *) begin if k < m a x then k: = k - \ - \ л ек [k] : = x end; begin (* лек си ка *) = ’PR O G R A M , слова [programc] = ’B E Q IN □ □ □ ; слова [beginc] = END □ □ □ □ □ ; слова [endc] с л о в а [varс] = ’VAR □ □ □ □ слова [integerc] = ’lN T E G E R □ ’; слова [dive] = DIV □ □ □ □ □ ’; слова [mode] = ’M OD □ □ □ □ □ ’; k: = 0 ; repeat read(c)\ , , , if с in [ A .. Я ] then (* слово *)


begin r\ 0; г: -*[ ! □ □ □ □ □ □ □ ’; while с in [А’..’Я ’, ’O’..’9 ’] do begin if r < 8 then r : = r + 1; z[r] : = c \ read(c) end; л к : = program c, while (слова [лк] ф г ) a n d (A K < m o d c ) do л к : = succ(a k )-, if слова [лк] = z then новая(лк) else новая(имя) end; if с in [ 0 .. 9 ] then (* число *) begin while с in [ 0 .. 9 ] do read (c); новая {число) end; if c = : then (* двоеточие или присваивание*) begin read(c)-, if c = = then (* присваивание *) begin новая(присваивание); read(c) end else (* двоеточие *) новая(двоеточие) end; if с in [’ + ’, ’ — V , ’(’, ’)’, ’;’] then case с of + , : новая (плюс)-, — '.новая (минус)-, '.новая (звездочка)-, ( : новая (л ск о б к а ); j '.новая (пскобка); • ; новая (точка)-, , ; новая (запятая)-, ; : uoikui (точкасзапятой) end (list* *) tuilll li ) ш I'll/, H O t l l l ' l ..................

•'

941


end (* лексика *) Формирование слова зак ан чи в ается тогда, когда послед­ ний из прочитанных символов с не принадлеж ит слову. Если с ф , то он входит в другую лексему. Вследствие этого прерывается действие одного длинного условного оператора, сод ерж ащ егося в схеме программы, и в том же самом цикле ан ализируется новая лексема. По той ж е при­ чине условный оператор прерывается и после ан ал и за лексем число, двоеточие, а т ак ж е присваивание. Запись лексем в л е к производится в нескольких местах, поэтому мы составили процедуру новая. В процедуре лек си ка все имена превращ аю тся в одну и ту ж е лексему имя, а все числа — в одну и ту ж е лексему число. Если бы мы хотели транслировать программу до конца, то их значения следовало бы сохранить и записать в таблицы (м а с с и в ы ). Мы не делали этого, так как продолжим т р ан сл я­ цию только до синтаксического ан али за (см. зад ач у 99), а идентификация значений чисел и имен для синтаксического ан ал и за не нужна. К аж ды й программист хорошо знает, что редко удается сразу написать программу без ошибок. Хороший транслятор долж ен быть «другом» программиста: обработать и ошибоч­ ную программу, а об ошибках проинформировать програм­ миста. Часть ошибок можно обнаружить уж е во время лексического ан ал и за. Д л я простоты мы не предусмотрели в программе сообщение об ошибках, а ошибки игнорировали. Например, если в тексте встретился символ, не п ри н ад л еж а­ щий подмножеству язы ка (например, ’Г, ’< ’ и т. д .), то он пропускается (приравнивается к пробелу). Если лексем слишком много, то все прочие лексемы пишутся в послед­ ний элемент массива лексем. Конечно, тогда сохраняется только последняя лексема. Потерянные лексемы почти всегда являю тся причиной синтаксических ошибок, которые позднее обнаруж иваю тся программой синтаксического анализа.

99. Синтаксический анализ Видный советский специалист по программированию М. Ш ура-Бура любит разнообразить серьезную работу программистов шуточными аксиомами. П ервая из них гласит, что «в любой программе есть ошибка». В самом деле, п редъ­ явив новую программу машине, мы почти всегда вместо ожидаемого решения зад ач и получаем сообщение об ош иб­ ках в этой программе. 242


Программировать было бы легче, если бы машина находи­ л а и исправляла все ошибки. К нашему счастью (или несчастью), дело обстоит не так. Немало ошибок, особенно запрятанны х поглубже, остается и на долю программиста. Однако машина всегда успешно находит синтаксические ошибки. Их можно обнаружить, анализируя текст программы при помощи синтаксических диаграмм. П р еж де всего р а з ­ берем, как нам самим, пользуясь диаграммами, искать ошибки в программе, а затем запрограммируем свои дей­ ствия. Предположим, у нас есть программа, вы раж ен н ая на подмножестве язы ка П аскаль (см. зад ач у 98): PRO GR AM О Ш И БК А ; VAR A: INTEGER; А: = 5 END. Вся программа д олж н а удовлетворять диаграмме про­ грамма (см. рис. 44). Проверим это. Первый символ на этой д иаграмме — служебное слово PROGRAM . Оно есть и в тек ­ сте программы. Значит, до сих пор программа правильна. Проверяем дальш е. Согласно синтаксической диаграмме следующим символом (лексемой) может быть только имя. Т ак и обстоит дело в тексте программы. Д ал ь ш е — точка с запятой. Она так ж е есть в программе. А после нее на диаграмме идет заголовок другой диаграммы — описания. Запоминаем это место диаграммы программа и переходим к диаграмме описания. П ер вая лексема на ней — слово VAR. Оно есть и в программе. Идем дальше. И в программе, и на диаграмме — имя. Д альш е. На диаграмме после имени может быть или двоеточие, или зап ята я. В программе — ■двоеточие. Д и а г р ам м а показывает, что дал ьш е может идти только слово IN TEG ER , а затем — только точка с запятой. Эти самые лексемы находим и в программе. Значит, пока ошибок не обнаружено. Д и а гр ам м а описания закончилась. В озвращ аем ся на диаграмму программа и проводим анализ с того места, в котором мы покинули эту диаграмму. Теперь — д иаграм м а блок. Она показывает, что первой лексемой блока долж но быть слово B EG IN . А в тексте программы — имя. Других альтернатив нет. Значит, мы нашли ошибку. Действительно, в этой программе пропущено служебное слово BEGIN. I'-iK-pi. составим программу, которая бы проверяла, \ |"м. и пч>|)нсг ли анализируемый текст программы синтакси­ ческим цимгрнммам. I' 'I Iук > iii.il р 1мм у естественно представить в виде 243


процедуры. Переход к новой диаграмме тогда будет и зо бр а­ ж аться обращением к ее процедуре, а возврат к прежней диаграмме — возвращением к процедуре прежней д и а ­ граммы. Будем считать, что перед тем уже проведен лексический анализ текста проверяемой программы (см. зад ач у 98), а ее лексемы со держ атся в массиве лек. Составим процедуру д ля диаграм м ы пр ограм м а: procedure пр ограм м а; begin if лек [k] = pro g ra m c then k: = k + \ else о ш и б к а ; if лек[1г] = и м я then k : = k - j - \ else о ш и б к а ; if л е к Щ = точкасзапятой then k : = k - \ - l ; опи сан ие; блок; if лек [k] Ф точка then ошибка end Аналогичным образом можно составить и процедуры для других диаграмм. В случае ошибки происходит обращение к процедуре ошибка. В ней долж ны быть предусмотрены все действия, связанные с ошибкой: какое сообщение напечатать, как исправить ошибку, как дальш е анализировать програм­ му и т. д. Именно эта процедура определяет, насколько хороша диагностика ошибок. Здесь мы составим самую простую процедуру, которая напечатает номер первой л ек­ семы, не удовлетворяющей синтаксической диаграмме, и пропустит эту лексему: procedure ошибка; begin if ещ ёнебыло then write (’Н Е П Р А В И Л Ь Н А Я Л Е К С Е М А № ’, k)\ if л ек [k ] Ф точка then ещ енебыло : = false end Л огическая переменная ещ енебыло д олж н а быть описана в основной части программы, и ей присваивается значение true, означающее, чтр пока в программе не встретилось ошибок. Теперь составим процедуры для всех диаграмм и, включим их в программу: p ro g r a m синтаксис (input, output)-, const m a x = 1000; (* максимальное число*) 244


(* лскс'(?м | I *) in,', мшар, :ше;и)очка, /ккобка, нековка, присваивание, двоеточие, точка, запятая, точкаезапятой, program e, beginc, endc, vare, in teg erc, dive, mode, имя, число)-, лексемы — a r r a y [1 ..max) of лексема-, v a r лек:лексем ы ; k : 0 ..max\ ещ енебы ло: boolean; (* ошибки *) p rocedu re лексика (var лек:лексемы); (* лексический ан али з *) ...(см. зад ач у 98) procedure ошибка; begin if ещ енебыло then write (’Н Е П Р А В И Л Ь Н А Я Л Е К С Е М А № , k); \\ лек[к]Фточка then k : = k - \ - \ ; ещ енебы ло : = false (* ошибка уж е была *) end; p rocedu re выражение; fo rw ard ; proced u re множитель; begin if лек [/г] in [число, имя] then /г: = = 6 + 1 else if лек [k] = л с к о б к а then begin k: = k - \ - 1; выражение; if л ек [k] — пскобка then k : = k - \ - \ else ошибка end else ошибка end; (* множитель *) procedure терм; begin множитель; while л е к Щ in [звездочка, dive, mode] do begin k:=k+\; множитель end end; (* терм *) procedure ubipiiw(’Hiw; begin type

jii' m

I'un

герм,

while ii'i \h | Hi b 111,1 hhhih'I do iMB


begin k:= k+ l; терм end end; (* выражение *) procedure предлож ение; begin if лек [k] — имя then k : = k - \ - \ else ошибка; if лек [k] = при сваи ва н ие then k ' . = k - \ - \ else ошибка', выражение end; (* предложение *) procedure блок-, begin if лек[1г] Ф beginc then ошибка else rep eat k:= k+ lпредложение until л е к [k] Ф точкасзапятой.-, if лек [k] = endc then k : = k - \ - \ else ошибка end; (* б лок *) procedure описание-, begin if л е к [ к ] — varc then begin repeat k: = k + l \ if л е к [k] — имя then k : = k - \ - \ else ошибка until л е к Щ ф запят ая; if л ек [&] — двоеточие then k : — k - \ - \ else ошибка-, if л е к Щ = integer с then k : = k - \ - \ else о ш и б к а ; if л е к [k] = точкасзапятой then k : = k - \ - \ else ошибка end end; (* описание *) procedure программа-, begin ■ if л е к [ 1 г ] = pro g ra m c then k : = k - \ - \ else ошибка-, if л ек [k ] = имя then k : — k- \ - \ else ошибка-, 246


I,

if

| / 1

.

(Ill'll

/.

/.' I

I

Им* ошибка, U llth о

II

iH

im

tini

ii'h

| Л-1

tuutUl

then

ошибка

e n d , (♦ программа *) be^ln ( + основная часть программы *)

лексика(лек)\ ещ енебы ло : = true', /г: = 1; программа end. Процедуры мы расположили в обратном порядке по сравнению с порядком синтаксических диаграмм на рисун­ ке 44. Ведь в языке П аскаль требуется, чтобы имена (в том числе и имена процедур) были описаны раньше, чем пере­ менные. В этой зад ач е имеем цепь процедур (выражение — множитель — терм — выражение — ...), поэтому мы пред­ варительно описали заголовок одной из них, используя директиву fo rw ard . Во всех процедурах синтаксических диаграмм исполь­ зуются одни и те же данные, поэтому удобнее обойтись без параметров.

100. Программа, печатающая самое себя На первый взгляд з а д а ч а каж ется очень простой — в программе нужно только написать операторы, печатающие ее текст. Недолго думая, пишем программу: p ro g ra m р (output)', begin writeln (’p ro g r a m p(output)\ ); writeln (’b eg in’); writeln (w r ite ln (’’p ro g r a m p(output); writeln (’writeln ( begin ); );

); );

Нам легко было написать два первых оператора, печатаю ­ щих первые две строки программы. Однако надо напечатать и ri'Kci >|цх операторов. Д л я этого мы написали ещ е два с>■и-1>.11<)| >.I I In для того чтобы напечатать тексты этих опера­ тором. мим шншдоГшгея еще дна, уже более длинных оп ер а­ тора II ini Л п м н и т liimuiT, мы идем не том путем. Надо и с к а п . мы v | I 247


Причина неудачи заклю чается в том, что текст, который может быть напечатан оператором, составляет лишь часть этого оператора. Например, оператор writeln (’ХХХ’) печа­ тает текст XXX. А в оператор печати входит не только этот текст, но и имя процедуры печати writeln, скобки, а так ж е ограничивающие строку апострофы. Значит, оператор печати долж ен напечатать текст более длинный, нежели его собственный текст. А это можно сделать, используя цикл или рекурсию. П рограм ма, печ атаю щ ая самое себя, в которой исполь­ зуется цикл, приведена, например, на рисунке 45. Это листинг, напечатанный ЭВМ, в котором можно точно определить число пробелов между символами (а это сущ е­ ственно для данной за д а ч и ). Приведенная программа со­ ставлена инженером Каунасского политехнического институ­ та Витаутасом Валайтисом. Попытайтесь осуществить все действия, записанные в программе, и удостоверьтесь, что программа в самом деле напечатает себя. Обратите внимание на то, как печатаются апострофы и конец текста программы, находящийся после операторов печати. © Составьте программу, печатающую самое себя, в которой нет циклов, а есть рекурсивная процедура. © © Составьте две программы: А и В. Програм ма А д олж н а печатать текст программы В, а программа В — текст программы А. Тексты программ А и В не долж ны быть идентичны. pro g r a m var с i begin c[ 1] = cl 2 J = с [ 3] = c[ 4] = с [ 5] = cl 6] = c[ 7] = cl 8] = cl 9 J = с [ 10 ] = c[ 1 1 J = Cl 12] = с [ 13 J cl 14] =

a u t o b i o g r a f i j a (output); : a r r a y [1. .14] of stringl60]; : integer; program a u t o b i o g r a f i j a (output); var с a r r a y [1..14] of string; i integer; begin for i := 1 to 4 do w r iteln(c[i ] ) ; for i := 1 to 13 do w r i t e l n ( c [ 1 4 , 1 ] ,c [ 14 , 2 ] , i : 2 , c [ 14,5], c[14,6],c[14,7],c[14,8],c[l],c[14,8],c[14,9]); for i := 1 to 8 do w r i t e (с I 1 4 , i ]); for i := 1 to 8 do w r i t e (с [14,i ]); for i := 8 to 60 do w r i t e (с I 14 , i ]); w r i t e l n (с [14,8],e [ 1 4 , 9 J ); for i := 5 to 13 do w r i t e l n (с Г i ] ).; end. c [ 14 J := ’ ’ ;

for i := 1

to 13 do w r i t e l n ( c [ 1 4 , l ] , c [ 1 4 , 2 ] , i : 2 , c l l 4 , 5 ] , с [ 14,6], c [ 14 ,7 ],с I 14,8], с [ i ], с [ 14 ,8 ],с [14,9]); for i := 1 to 8 do wr i te (cl 14 ,i ] ).; for i 1 to 8 do w r i t e (с [14,i 1); for i := 8 to 60 do w r i t e (с [14,i j ); w r i t e l n { с [ 1 4 , 8 J ,с [ 1 4 , 9 J ); for i := 5 to 13 do w r i t e l n (с [i J ); end .

Рис. 45. Себя печатаю щ ая програм м а.

’ ’ ' » ’

’ ’ * * ’


ЗАКЛЮ ЧЕНИЕ Читатель, у которого будет т а к а я возможность, без( сомнения, захочет выполнить некоторые наиболее интересные программы на ЭВМ. П рограм ма, составленная на языке П аскаль, д о л ж н а была бы хорошо подходить для любой ЭВМ, располагаю щ ей транслятором этого языка. Однако на практике встречаются небольшие отклонения. Они возникают из-за специфики конкретных ЭВМ, чащ е всего связанной с вводом или выводом данных, а так ж е из-за того, что язык П а ска л ь не весь является окончательно определенным, что он в какой-то степени видоизменяется, совершенствуется. Поэтому, прежде чем вводить программу в машину, надо познакомиться с описанием используемого в ней языка Паскаль. Здесь мы не будем рассматривать специфику конкретных ЭВМ или их трансляторов, а только упомянем то, на что программист долж ен обратить внимание при вводе програм­ мы в ЭВМ и знакомстве с ее трансляторами. Почти во всех тран сляторах ал ф ави т П а ск а л я отож деств­ ляется с алфавитом ЭВМ. Многие ЭВМ располагаю т только заглавными латинскими буквами. В них нет букв русского алф авита. Буквы в алфавите (кодах) ЭВМ расположены в том же порядке, что и в латинском алфавите, однако между группами букв имеются неиспользуемые коды, вследствие чего результатом функций succ, pred, chr может о казаться несущ ествующая буква. Символы операций ^ ^ и ф кодируются в алфавите ЭВМ парами символов: < =, > = и < >. Но всех трансляторах предусмотрены ввод и вывод чисел и символов, a is некоторых — и других значений (логиче­ ских, скалярных, строковых — упакованных массивов сим­ волов) 249


Основная литература 1. Г р и г а с Г. Н а ч а л а программирования.— М.: П ро­ свещение, 1987. 2. В а н Т а с с е л Д. Стиль, разработка, эффективность, отладка и испытание программ.— М., 1981. 3. В и р т Н. Систематическое программирование.— М., 1977. 4. Г р о г о н о П. Программирование на языке П а ска ль .— М., 1982. 5. Д е й к с т р а Э. Дисциплина программиоования.— М., I9786. Й е н с е н К-, В и р т Н. П аскаль: Руководство для пользователя и описание язы к а .— М., 1982. 7. К н у т Д. Искусство программирования для ЭВМ. Получисленные алгоритмы.— М., 1977.— Т. 2. 8. У э з е р е л л Ч. Этюды для программистов.— М., 1982.

Дополнительная литература 9. Б е р м а н Г. Число и наука о нем.— М., 1972. 10. Д о м о р я д А. М атематические игры и развлечения.— К., 1972. 11. Г а р д н е р М. Математические досуги.— М., 1972. 12. К о р д е м с к и й Б. М атематическая см екалка.— 7-е изд., перераб.— М., 1963. 13. Л и т ц м а н В. Теорема П и ф аго р а .— М., 1960. 14. Л и т ц м а н В. Веселое и занимательное о числах и фигурах.— М., 1963. 15. О р е О. Приглашение в теорию чисел.— М., 1980. 16. П е р е л ь м а н Я- Зани м ател ьн ая ал геб ра.— М., 1978. 17. П е р е л ь м а н Я. Зани м ател ьн ая арифметика. Загадки и диковинки в мире чисел.— М., 1959. 18. П е р е л ь м а н Я. Зани м ател ьн ая геометрия.— М., 1959. 19. Ш т е й н г а у з Г. Математический калейдоскоп.— М., 1981. 20. В и л е н к и н Н. К омбинаторика.— М., 1969. 252


Содержание

П р е д и с л о в и е ............................................................................................................... В в е д е н и е ...................................................................................................................... 1. Ф акториал ........................................................................................................ 2. О сторож но: m a x i n t l ......................................................................................... 3. Разм ещ ен и я и с о ч е т а н и я ................................................................................. 4. Числа Ф и б о н а ч ч и ................................................................................................ 5. Суммы р я д о в ........................................................... ............................................ 6. Возведение в к вад р ат без операции ум нож ения . . . . . . 7. Извлечение корня из действительны х ч и с е л ..................................... 8. Корни из натуральны х ч и с е л .................................................... 9. И звлечение квад ратного корня из натуральны х чисел . . . . 10. А рифметический к в а д р а т .......................................................................... 11. Т реугольник П а с к а л я ......................................................................................... 12. Т р е у г о л ь н и к ........................................................................................................ 13. П иф агоровы ч и с л а ......................................................................................... 14. Р азр езан и е прям оугольника на к в а д р а т ы ............................................ 15. Равновеликие п р я м о у г о л ь н и к и ................................................................... 16. Равновеликие т р е у г о л ь н и к и ..........................................................................

5 7 14 17 19 20 24 27 28 31 32 34 36 40 41 44 46 47

17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. !1Г>

50 57 59 61 62 63 65 67 69 72 74 76 77 82 83 90 92 93 95

Уравнение a 3 + 63 = c3 + d 3 .......................................................................... З а д а ч а А н танаса Б а р а н а у с к а с а ................................................................... С читалка ............................................................................................................... Д ел еж ....................................................................................................................... Н аибольш ий общ ий д е л и т е л ь ................................................................... Взаимно простые ч и с л а ..............................* . . . . . . . Н аименьш ее общ ее к р а т н о е .......................................................................... Б и л ь я р д ............................................................................................................... Д елители ............................................................................................................... Простые ч и с л а ................................................................................................ Числа М е р с е н н а ........................................................... ....... Р азл ож ен и е на простые м н о ж и т е л и ........................................................... Эратосф еново р е ш е т о ................................................................................. Б л и з н е ц ы ............................................................................................................... С катерть У л а м а ................................................................................................ Соверш енные ч и с л а ......................................................................................... Д р у ж п тш-иныг ч и с л а ................................................................................. Цифры ................................................................................. Лнтоморфмые мнг.чн .................................................................................

253


36. 37. 38. 39. 40. 41. 42.

Н ум ерация книж ны х с т р а н и ц ........................................................... С частливы е троллейбусны е б и л е т ы ..................................... Сумма кубов ц и ф р ......................................................................................... 100 Ч и сла А р м с т р о н г а ......................................................................................... 101 К вадраты , состоящ ие из разны х ц и ф р ....................................................104 1! + 4! + 5 ! = 1 4 5 .................................................................................................106 Н аименьш ее число не всегда м а л о ........................................................... 109

43. 44. 45. 46. 47. 48. 49. 50.

Д воичны е числа . ......................................................................................... 111 Ш естнадцатеричная с и с т е м а .......................................................................... И З П а л и н д р о м ........................................................................................................115 Больш ое п р о и з в е д е н и е ..................................................................................117 Больш ие ч и с л а .................................................................................................119 Точность действительных ч и с е л ...................................................................121 Точное ч а с т н о е ......................................................................................... ....... Д еление с больш ой т о ч н о с т ь ю ...................................................................122

51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81.

П росты е д р о б и ........................................................... .....................................125 Д есятичны е д р о б и ......................................................................................... 127 С таринны е м е р ы .................................................................................................130 Д ействительны е числа и б у х г а л т е р и я ....................................................131 Высота музы кального з в у к а .......................................................................... 133 С лучайны е ч и с л а .................................................................................................134 Вычисление значения л путем бросан ия и г л ы ..................................... 136 П л о щ ад ь ф и г у р ы ......................................................................................... ....... 140 Д а т ы ...................................................................................................................... 141 Д а т а следую щ его д н я ..................................................................................145 Б у д у щ ая д а т а ........................................................................................................ 146 Число дней м еж ду датам и ' .......................................................................... 147 Д ен ь н е д е л и ........................................................................................................149 Д ен ь р о ж д е н и я ..................................................................., . . . 151 Сто лет с е м ь е ........................................................................................................- 1 5 2 Биологические ритмы . .......................................................................... 155 Н астенный к а л е н д а р ь ..................................................................................159 Л унны й к а л е н д а р ь ......................................................................................... 163 Рисование геометрических ф и г у р ........................................................... 168 К вад р ат из ц и ф р ..................................... ...........................................................169 Симметричные ф и г у р ы ..................................................................................170 О р н а м е н т ы ........................................................................................................172 Символы больш ого ф о р м а т а .......................................................................... 175 Ч астота повторения б у к в ..................................................................................178 Ч астота повторения с л о в ..................................................................................179 П илообразны й т е к с т ......................................................................................... 181 Б и л и с т и н г ............................................................................................................... 183 Техническое редактирование текста ............................................................184 Голландский ф л а г .........................................................................................188 Сортировка д а н н ы х ........................................................... 192 Сортировка методом « п у з ы р я » ...................................................................194

254

96 98


I 1 Бы страя с о р т и р о в к а .........................................................................................196 ■i 11 о и с к ...................................................................................................................... 198 Ml, Римские ц и ф р ы ................................................................................................ 200 HI). Почтовые и н д е к с ы .........................................................................................202 .................................................................................................204 Hli. А збука М орзе Н7. Ш ифр Ц е з а р я ................................................................................................ 205 88. Ш ифр Г р о н с ф е л ь д а .........................................................................................206 89. К о р о л ь ...............................................................................................................208 90. Ханойские б а ш н и .........................................................................................210 91. И гра « Ж и з н ь » ................................................................................................ 212 92. И гра в с п и ч к и ................................................................................................ 217 93. С удья с о р е в н о в а н и й .........................................................................................219 94. Л а б и р и н т ........................................................................................................221 95. О бход ш ахм атной доски ходом к о н я ..........................................................226 96. Восемь ф е р з е й ................................................................................................ 228 97. Р а скр аска к а р т ы ................................................................................................ 232 98. Л ексический а н а л и з .........................................................................................236 99. Синтаксический а н а л и з ................................................................................. 242 100. П рограм м а, печатаю щ ая сам ое с е б я ....................................................247 З а к л ю ч е н и е ...............................................................................................................249 С ловарь основных терминов язы ка П а с к а л ь ....................................................250 О сновная л и т е р а т у р а ................................................................................................ 252 Д оп олн ительн ая л и т е р а т у р а ..................................................................................

255


Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.