/
ISBN: 5-02-013731-6
Text
1
Валерий Рубанцев
Если бы у Эйлера был компьютер.
Решаем сложные и олимпиадные задачи
Все права защищены. Никакая часть этой книги не может быть воспроизведена в любой
форме без письменного разрешения правообладателей.
Автор книги не несёт ответственности за возможный вред от использования информации, составляющей содержание книги и приложений.
Copyright 2026 Валерий Рубанцев
Лилия Рубанцева
2
От автора
Не боги горшки обжигают.
В книге Если бы у Диофанта был компьютер мы решали исторические задачи по математике. Они довольно простые, поэтому хороши для тренировки, но иногда хочется и по-настоящему поломать (себе) голову. Для
этого и предназначены олимпиадные задачи. Мы не будем замахиваться
на сложные задачи международного уровня, которые скорее трудны, чем
увлекательны, а решим несколько десятков вполне олимпиадных задач, которые интересны и сами по себе, и как упражнения в практическом программировании.
Большая часть задач взята с сайта Проект Эйлер, который и дал название
этой книге. Также мы решим парочку задач, придуманных самим Эйлером.
3
Не ограничивая себя олимпиадными задачами, мы дерзнём замахнуться и
на другие, более занимательные, но тоже непростые задачи – Клайва Синклера, Станислава Улама, Крайчика, Мадахи, Гарднера, Армстронга, Пиковера и Левенштейна. В такой компании скучно не будет!
Обычно олимпиадные задачи решаются с большим трудом или не решаются вообще без какой-либо догадки. Как вовремя догадаться, не знает никто. Но, с другой стороны, есть определённый набор приёмов, которые помогают решать нестандартные задачи. И чем больше таких приёмов вы
имеете в своём арсенале, тем легче вам будет с ними справляться.
При решении задач мы будем использовать модули, которые я разработал
для этой книги. Они содержат много методов расширения и генераторов
различного рода последовательностей, поэтому пригодятся при решении
других, возможно, более сложных задач.
Эта книга только познакомит вас с огромным миром олимпиадных задач - с
надеждой, что это подтолкнёт вас к более глубокому изучению программирования на языке паскаль, который идеально подходит для решения математических задач и для быстрой разработки и отладки разнообразных проектов.
Я надеюсь, что эта книга будет полезна:
Учителям информатики и математики
Любителям различного рода задач и головоломок
Начинающим программистам и любителям программирования
Родители, которые хотели бы привлечь детей к программированию и тем
самым развить их логическое, алгоритмическое мышление.
Автор
4
Условные обозначения, принятые в книге:
Дополнение или замечание
Требование или указание
Исходный код:
begin
Writeln(' Project Euler. Problem 33');
WriteLn;
Solve;
end.
Заголовок проекта:
Проект …
Исходные коды всех проектов находятся в папке _Projects
5
Оглавление
Если бы у Эйлера был компьютер. ..................................... 2
Решаем сложные и олимпиадные задачи.............................. 2
От автора ............................................................... 3
Оглавление ............................................................. 6
Олимпийские задачи .................................................. 9
Проект
Проект
Проект
Проект
Проект
Олимпиадная задача ............................................................................... 10
Сумма чисел ................................................................................................ 11
Режем прямоугольники........................................................................... 14
Двузначные числа ..................................................................................... 19
Задача Клайва Синклера ......................................................................... 22
Project Euler ............................................................................................................... 31
Проект Эйлер001 ..................................................................................................... 34
Проект Эйлер002 ..................................................................................................... 38
Проект Эйлер003 ..................................................................................................... 43
Проект Эйлер004 ..................................................................................................... 46
Проект Эйлер005 ..................................................................................................... 54
Проект Эйлер006 ..................................................................................................... 58
Проект Эйлер007 ..................................................................................................... 61
Проект Эйлер008 ..................................................................................................... 63
Проект Эйлер009 ..................................................................................................... 74
Проект Эйлер010 ..................................................................................................... 80
Проект Эйлер011 ...................................................................................................... 83
Проект Эйлер012 ..................................................................................................... 87
Проект Эйлер013 ..................................................................................................... 93
Проект Эйлер014 ................................................................................................... 100
Проект Эйлер015 ................................................................................................... 105
Проект Эйлер016 ....................................................................................................112
Проект Эйлер017 ....................................................................................................114
Проект Эйлер018 ....................................................................................................121
Проект Эйлер067 ................................................................................................... 128
6
Проект Эйлер019 ....................................................................................................131
Проект Эйлер020 ................................................................................................... 136
Проект Эйлер021 ................................................................................................... 138
Проект Эйлер022 ................................................................................................... 146
Проект Эйлер023 ................................................................................................... 149
Проект Эйлер024 ................................................................................................... 154
Проект Эйлер025 ................................................................................................... 157
Проект Эйлер026 ....................................................................................................161
Проект Эйлер027 ................................................................................................... 166
Проект Эйлер028 ....................................................................................................171
Проект Спираль Улама ........................................................................................ 173
Проект Эйлер029 ................................................................................................... 193
Проект Эйлер030 ................................................................................................... 195
Проект Эйлер031 ................................................................................................... 198
Проект Эйлер032 .................................................................................................. 200
Проект Эйлер033 .................................................................................................. 206
Проект Эйлер034 ................................................................................................... 210
Проект Эйлер035 ................................................................................................... 214
Проект Эйлер036 ................................................................................................... 217
Проект Эйлер037 .................................................................................................. 222
Проект Эйлер038 .................................................................................................. 227
Проект Эйлер043 .................................................................................................. 233
Проект Эйлер041 .................................................................................................. 238
Проект Эйлер104 .................................................................................................. 242
Проект Эйлер142 .................................................................................................. 247
Проект
Проект
Проект
Проект
Проект
Проект
Проект
Проект
Проект
Пандигитальные квадраты .................................................................. 251
Кратные пандигитальные числа ....................................................... 258
Enigma 1436: One more step ................................................................ 262
Enigma 1496: Eighteen ........................................................................... 268
Enigma 1504: All ten digits ................................................................... 272
Enigma 1518: Sets of squares .............................................................. 286
Задача Крайчика #3 ............................................................................... 291
Задача Мадахи #22 .............................................................................. 293
Задача Мартина Гарднера.................................................................... 295
7
Проект Высокая степень искусства ................................................................. 298
Проект Числа Армстронга.................................................................................. 302
Словесные дистанции .......................................................................................... 310
Проект Алгоритм Левенштейна .........................................................................311
Проект Шестизначный перенос ........................................................................ 320
Проект Найдите число......................................................................................... 321
Проект Паразитические числа ......................................................................... 333
Проект Циклические числа ............................................................................... 337
Проект Супержесть ............................................................................................... 349
Литература ........................................................ 355
8
Олимпийские задачи
9
Проект Олимпиадная задача
Исходный код программы находится в файле Олимпиадная задача.pas.
Первая олимпиадная задача должна быть очень простой!
В своё время на городской олимпиаде по математике я решал такую задачу:
Подсчитайте, сколько раз пятёрка входит в представление чисел от 1 до
1000 в виде произведения простых чисел: 2, 3, 5, 7, 11,… (например, 24 = 2 * 2*
2 * 3; 25 = 5 * 5).
Простенькая программа поможет нам решить задачу:
##
// Олимпиадная задача
// счётчик пятерок:
var n5 := 0;
// проверяем все заданные числа:
for var i := 1 to 1000 do begin
var n := i;
var flg := true;
while (flg) do begin
// если число делится на 5,
// увеличиваем счетчик пятерок:
if (n mod 5 = 0) then begin
n := n div 5;
n5 += 1;
PrintLn($' Число = {i}
n5 = {n5}');
end
else flg := false;
end // while
end;
// for
WriteLn(' Число пятёрок =', n5);
Здесь вы легко найдёте цикл for, в котором перебираются все заданные
числа, и вложенный в него цикл while, который и подсчитывает их
10
делители. Если число нацело делится на 5, то счётчик увеличивается на 1,
иначе мы переходим к проверке следующего числа. Олимпиадный «подвох»
этой задачи заключается в том, что полученное после деления на 5 число
опять может быть кратно 5, то есть его снова нужно проверить. Если это
обстоятельство не учесть, то число пятёрок можно было бы подсчитать
мгновенно: 1000 : 5 = 200.
Таким образом, в первой тысяче чисел ровно 200 делятся на 5. Ещё 40 делятся на 25 (5*5), 8 – на 125 (5*5*5) и одно – на 625 (5*5*5*5)!
Компьютер справился с олимпиадной задачей!
Число пятёрок = 249
Проект Сумма чисел
Исходный код программы находится в файле Сумма чисел.pas.
Вторая задача – не олимпиадная, но она на простом примере показывает
суть рекурсивного решения многих, в том числе и олимпиадных задач.
Первоисточник – интерактивный учебник (а также его перевод на русский
язык) Problem Solving with Algorithms and Data Structures.
11
Суть рекурсии заключается в разбиении задачи на более мелкие подзадачи
– до тех пор, пока не останется такая подзадача, которая решается элементарно.
Так как все подзадачи (кроме, может быть, самой «мелкой») главной задачи
решаются однотипно, то достаточно одной функции, которая рекурсивно
вызывает сама себя с разными аргументами.
А теперь перейдём непосредственно к задаче, которая очень проста: подсчитать сумму элементов в списке. Понятно, что решение задачи не зависит от длины списка, поэтому в условии задачи список включает всего 5 однозначных чисел.
Поскольку мы заранее знаем, что в списке ровно 5 чисел, то можем использовать для их хранения обычный массив, но список типа List – более универсальная структура данных, поэтому может пригодиться вам при развитии этой задачи.
Итак, в главной программе мы создаём список и заполняем его числами:
begin
// сумма чисел:
var a := | 1, 3, 5, 7, 9 |.ToList;
В реальной программе мы легко нашли бы сумму элементов списка, просто
вызвав метод расширения Sum:
var sum := a.Sum;
writeln($' Сумма чисел = {sum}');
Но в задаче предполагается, что мы будем честно пересчитывать все элементы списка самостоятельно. Это легко сделать в цикле, однако условие
задачи запрещает использовать любые циклы, поэтому этот способ не
годится.
12
Но мы можем заменить любой цикл рекурсивными вызовами функции
SumRec:
// РЕКУРСИВНАЯ ФУНКЦИЯ
function SumRec(nums: List<integer>): integer;
begin
if (nums.Count = 1) then
result := nums.First
else
result := nums.First + SumRec(nums.Skip(1).ToList);
end;
При каждом её вызове к переменной result добавляется первый элемент в
списке, а затем снова вызывается эта же функция, но с новым списком, в котором отсутствует уже посчитанный первый элемент предыдущего списка.
Таким образом, каждый раз из списка удаляется его первый элемент, значит, список становится короче на 1 элемент с каждым вызовом функции.
В функции SumRec нетрудно усмотреть элементарную подзадачу: рекурсивные вызовы заканчиваются, когда в списке останется единственный
элемент. Сумма такого списка, естественно, равна самому элементу.
Рекурсивные вызовы завершаются, и функция начинает снимать со стека и
складывать в сумму все элементы исходного списка. Совершенно очевидно,
что так мы найдём сумму элементов любого списка:
begin
// сумма чисел:
var a := | 1, 3, 5, 7, 9 |.ToList;
var sum := a.Sum;
writeln($' Сумма чисел = {sum}');
sum := SumRec(a);
writeln($' Сумма чисел = {sum}');
writeln;
end.
13
Сумма чисел = 25
Сумма чисел = 25
Подобным же способом можно решать любые задачи, в которых используются циклы.
Проект Режем прямоугольники
Исходный код программы находится в файле Режем прямоугольники.pas.
Эта задача уже по-настоящему олимпиадная:
От заданного прямоугольника каждый раз отрезается квадрат максимальной площади (длины сторон фигур выражаются натуральными числами).
Найти число таких квадратов.
Её решение есть в книге Практикум программирования на Turbo Pascal.
Мы решим задачу несколько иначе, но отличия непринципиальные.
Сначала мы получаем от пользователя 2 числа – a и b – длины сторон исходного прямоугольника:
begin
Writeln(' Режем прямоугольники');
var (a, b) := ReadInteger2(' Введите длину сторон прямоугольника
(натуральные числа:');
И вызываем функцию Solve для подсчёта квадратов:
var n := Solve(a, b);
14
Println($' Число квадратов = {n}');
Writeln;
end.
В функции Solve цикл while считает отрезаемые квадраты в переменной
n:
// РЕШАЕМ ЗАДАЧУ
function Solve(a, b: integer): integer;
begin
var n: integer;
while (a * b > 0) do begin
if (a >= b) then begin
n += a div b;
a := a mod b;
end
else begin
n += b div a;
b := b mod a;
end;
end;
Result := n;
end;
Здесь нужно рассмотреть 2 случая:
1. Сторона a не короче стороны b.
2. Сторона a короче стороны b.
В первом случае мы отрезаем от прямоугольника a div b квадратов с длиной
стороны b. В результате длинная сторона a укорачивается до a mod b. Когда
одна из сторон станет равной нулю, все квадраты будут отрезаны, и решение закончится.
Второй случай аналогичен первому, только нужно поменять местами стороны а и b.
15
Цикл while (книжное решение) мы легко заменим вызовом функции
SumRec:
n := SumRec(a, b);
Println($' Число квадратов = {n}');
Writeln;
end.
Рекурсивную функцию SumRec легко написать по аналогии с циклом while:
// РЕШАЕМ ЗАДАЧУ РЕКУРСИВНО
function SumRec(a, b: integer): integer;
begin
if (a * b > 0) then begin
if (a >= b) then
result := a div b + SumRec(a mod b, b)
else
result := b div a + SumRec(a, b mod a);
end;
end;
Как вы видите, рекурсивная функция получилась очень простая и даже короче, чем цикл while.
При длине сторон прямоугольника 135 и 40 единиц (книжные значения) от
него можно отрезать 8 квадратов:
Режем прямоугольники
Введите длину сторон прямоугольника (натуральные числа): 135 40
Число квадратов = 8
Число квадратов = 8
Чтобы лучше понять действие алгоритма, прибегнем, как обычно, к наглядному рисованию.
16
Размеры прямоугольника уменьшим в 5 раз, чтобы на рисунке можно было
разглядеть клеточки. На самом решении пропорциональное изменение размеров, конечно, никак не отразится.
Тогда вначале мы имеем прямоугольник со сторонами 27 и 8 клеток:
Сторона отрезаемого квадрата всегда равна меньшей стороне прямоугольника. В нашем случае – 8. Начинаем отрезать от прямоугольника такие
квадраты, пока это возможно.
Всего мы сможем отрезать a div b таких квадратов, если a – большая сторона прямоугольника. От нашего прямоугольника мы отрежем 3 квадрата
8 х 8 клеток:
И от него останется прямоугольный кусок 8 х 3 клетки.
Запоминаем: меньшая сторона после отрезания становится большей, и
наоборот.
17
От прямоугольника 8 х 3 клетки мы сможем отрезать 2 квадрата 3 х 3
клетки:
Длина сторон квадрата, как мы уже выяснили, равна длине меньшей стороны прямоугольника.
Теперь остался прямоугольник 3 х 2 клетки, от которого можно отрезать 1
квадрат 2 х 2 клетки:
И от последнего прямоугольника 2 х 1 клетку отрезаем 2 единичных квадрата:
18
На этом разрезании от исходного прямоугольника ничего не осталось, а
число квадратов легко подсчитать – их ровно 8 штук.
Пользуясь нашим визуальным алгоритмом, мы можем написать новую версию рекурсивной функции:
function SumRec2(a, b: integer): integer;
begin
if (b > 0) then
result := a div b + SumRec2(b, a mod b)
end;
В главной программе мы вызываем функцию SumRec2 и в ответ получаем
ответ:
n := SumRec2(a, b);
Println($' Число квадратов = {n}');
Writeln;
Проект Двузначные числа
Исходный код программы находится в файле Двузначные числа.pas.
Эта задача также из книги Практикум программирования на Turbo Pascal.
Задана последовательность двузначных натуральных чисел. Длина последовательности неизвестна, а признаком её конца служит 0.
Напечатать члены последовательности в неубывающем порядке.
19
Поскольку длина последовательности заранее неизвестна, то для её хранения разумно использовать список. В Турбопаскале такая структура данных
отсутствует, поэтому элементы нужно хранить в массиве, длину которого
следует знать заранее. Возникает противоречие, для разрешения которого
нужна догадка!
Действительно, последовательность может быть очень длинной, но все её
элементы – это двузначные числа от 10 до 99. А это значит, что в массиве
можно хранить не сами числа, а их кратность в заданной последовательности (числа в последовательности могут повторяться!). Тогда для решения
задачи достаточно целочисленного массива с индексами 10..99.
Чтобы не решать уже решённую задачу, мы вспомним, что у нас имеется
PascalABC.NET, а в нём – класс List, в экземплярах которого котором удобно
хранить числовую последовательность любой длины.
Мы не будем вводить множество чисел с клавиатуры, как это делается в
книге, а имитируем этот процесс. При этом мы предполагаем, что «вводимые» числа не обязательно двузначные, поэтому добавляем в список
только те из них, которые удовлетворяют условию задачи:
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// создаём список для хранения последовательности:
var lst := new List<integer>();
// заполняем его числами:
var max := 200;
var nelem := Random(200, 1000);
for var i := 0 to nelem do begin
var num := Random(max + 1);
if num.InRange(10, 99) then
lst.Add(num);
end;
. . .
begin
Writeln(' Двузначные числа');
Writeln;
Solve();
20
end.
А всё решение задачи укладывается в две строки:
// сортируем список:
lst.Sort();
lst.Println;
Writeln;
end;
Или даже в одну:
lst.OrderBy(n -> n).Println;
Если вы незнакомы с LINQ, то можете распечатать список с помощью цикла
foreach:
foreach var i in lst do
write(i: 3);
Наше решение хоть и отличается от книжного, но тоже красивое!
Двузначные числа
10 10 11 11 11 12 12 12 13 13 13 13 13 13 15 15 15 15 15 16 16 16 16 17
17 18 18 18 18 19 20 21 22 22 22 23 24 24 24 25 25 25 26 26 26 27 27 28
28 28 29 29 29 29 30 30 31 31 32 32 32 32 33 33 33 33 33 34 34 37 37 38
38 39 40 41 41 41 41 41 41 41 42 42 42 42 42 43 43 44 44 45 46 47 47 47
47 47 47 48 48 49 49 49 49 49 50 51 51 51 51 52 52 52 52 52 53 53 53 54
54 54 55 55 55 56 57 57 57 57 58 58 59 59 59 59 60 61 61 61 62 63 63 63
63 63 63 64 64 64 65 65 65 66 66 66 66 66 67 67 68 69 69 70 71 72 72 72
72 72 73 73 73 75 75 75 76 76 76 76 76 76 76 77 77 77 77 77 79 79 79 79
80 80 81 81 81 82 82 82 82 82 82 83 83 84 84 84 84 84 85 85 85 85 86 86
87 88 88 88 89 89 90 90 90 90 91 91 91 92 92 92 93 93 94 95 95 95 96 96
96 97 97 97 97 97 97 98 98 99 99 99
21
Проект Задача Клайва Синклера
Исходный код программы находится в файле Задача Клайва Синклера.pas.
Эта задача почти олимпиадная. Её придумал сэр Клайв Синклер (тот самый
Синклер, который сделал компьютер ZX Spectrum, в своё время очень популярный в Советском Союзе).
А задача такая: подсчитать число разных прямоугольников в сетке 9 х
9 клеток. Поскольку квадрат - это тоже прямоугольник, то самый первый
квадратик уже можно посчитать. Для уменьшения расхода бумаги мы
22
дальше будем рассматривать сетку размером 3 х 3 клетки, а затем обобщим
задачу.
Если мы будем расширять первый прямоугольник по ширине и высоте, то
всего получим 9 прямоугольников.
23
Если мы переместим угловой квадрат на 1 и 2 строки ниже, а затем выполним те же «мероприятия», то насчитаем ещё 9 прямоугольников.
Точно так же мы подсчитаем прямоугольники для случая, когда единичный
квадрат находится в первой и во второй колонках сетки.
24
Итого для сетки 3 х 3 мы насчитали 36 разных прямоугольников. Давайте
разберёмся, как получилась эта сумма:
1. Мы передвигаем единичный квадрат от нулевой колонки до колонки
3-1 = 2 (нумерация начинается с нуля). На рисунках хорошо видно, что по
ширине всегда получается 3-col прямоугольников.
То есть:
для col = 0 → 3
для col = 1 → 2
для col = 2 → 1
2. Мы передвигаем единичный квадрат на всех колонках от нулевой
строки и до строки 3-1=2. Каждый раз по высоте получается n-row прямоугольников.
То есть:
для row = 0 → 3
для row = 1 → 2
25
для row = 2 → 1
3. Перемножаем число прямоугольников по ширине на число прямоугольников по высоте для всех значений col и row и добавляем к общей
сумме. В итоге мы получаем число всех прямоугольников в сетке.
И нам нужно только вместо размерности сетки 3 поставить число n – и задача решена!
Итак, в главной программе пользователь вводит число – размерность сетки.
Она всегда квадратная, поэтому достаточно одного числа.
Вы легко обобщите задачу для прямоугольных сеток, но тогда пользователь должен вводить два числа.
Подсчёт прямоугольников мы проведём в функции CalcRects, а главная
программа напечатает их число:
begin
Writeln(' Задача Клайва Синклера');
Writeln;
// бесконечный цикл ввода данных // пока пользователь не закроет программу:
while (true) do begin
var number := ReadInteger(' Введите размерность квадрата (1..) >');
// находим число прямоугольников:
var nRects := CalcRects(number);
// печатаем ответ:
Writeln($' Число прямоугольников = {nRects}');
Writeln;
end;
end.
Функцию CalcRects кодируем по нашему алгоритму:
26
•
Число прямоугольников храним в переменной res.
•
Изменяем номер колонки с единичным квадратиком от 0 до n-1.
•
Изменяем номер строки с единичным квадратиком от 0 до n-1.
•
Подсчитываем число новых прямоугольников для текущего значения
col и row = 0..n-1.
•
Добавляем новые прямоугольники к общей сумме res:
// ПРОГРАММА ДЛЯ РЕШЕНИЯ ЗАДАЧИ
// КЛАЙВА СИНКЛЕРА
// ПОДСЧИТЫАЕМ ПРЯМОУГОЛЬНИКИ
function CalcRects(n: integer): integer;
begin
// число прямоугольников:
var res := 0;
// по ширине сетки:
for var col := 0 to n - 1 do begin
// число прямоугольников по ширине:
var nCol := n - col;
// по высоте сетки:
for var row := 0 to n - 1 do begin
// число прямоугольников по высоте:
var nRow := n - row;
// добавляем:
res += nCol * nRow;
end;
end;
Result := res;
end;
Запускаем программу и находим число прямоугольников в любых квадратных сетках.
Задача Клайва Синклера
Введите размерность квадрата (1..) > 1
27
Число прямоугольников = 1
Введите размерность квадрата (1..) > 2
Число прямоугольников = 9
Введите размерность квадрата (1..) > 3
Число прямоугольников = 36
Введите размерность квадрата (1..) > 4
Число прямоугольников = 100
Введите размерность квадрата (1..) > 5
Число прямоугольников = 225
Введите размерность квадрата (1..) > 6
Число прямоугольников = 441
Введите размерность квадрата (1..) > 7
Число прямоугольников = 784
Введите размерность квадрата (1..) > 8
Число прямоугольников = 1296
Введите размерность квадрата (1..) > 9
Число прямоугольников = 2025
Введите размерность квадрата (1..) > 10
Число прямоугольников = 3025
Для задачи Синклера ответ такой: 2025 прямоугольников.
На этом можно было бы и закончить решение задачи, но трудно не обратить
внимание на то, что число прямоугольников всегда (по крайней мере, для
наших значений сетки) равняется квадрату какого-либо числа:
1 – 12
9 – 32
36 – 62
100 – 102
225 – 152
441 – 212
28
784 – 282
1296 – 362
2025 – 452
3025 - 552
На сайте oeis.org эта же последовательность чисел скрывается под кодом
A000537:
0, 1, 9, 36, 100, 225, 441, 784, 1296, 2025, 3025, 4356, 6084,
8281, 11025, 14400, 18496, 23409, 29241, 36100, 44100, 53361,
64009, 76176, 90000, 105625, 123201, 142884, 164836, 189225,
216225, 246016, 278784, 314721, 354025, 396900, 443556, 494209,
549081
И она не имеет никакого отношения к нашей задаче, а показывает сумму n
первых кубов:
03 = 0
03+13 = 1
03+13+23=9
03+13+23+33=36
03+13+23+33+43=100
. . .
С другой стороны, последовательность 1, 3, 6, 10,
15, 21, 28, 36, 45, 55,… представляет собой треугольные числа.
Треугольные числа – это разновидность фигурных чисел, о которых можно прочитать, например, в книге Мартина Гарднера Путешествие во
времени.
29
На рисунке⬆ видно, что треугольные числа можно найти как сумму членов
арифметической прогрессии:
1 + 2 + 3 + 4 + 5 + … = n(n+1)/2,
где n – номер треугольного числа.
Решением задачи Клайва Синклера служат квадраты этих чисел, поэтому
число прямоугольников в сетке размерами n x n клеток можно сразу же вычислить по формуле:
(n(n+1)/2)2
Подставляем n = 9 и получаем ответ:
30
(9х10/2)2 = 452 = 2025
Теперь сведём наши числовые наблюдения воедино и получим забавную
формулу, связывающую кубы и квадраты чисел (а также треугольные
числа):
03 = 0
03+13 = (0 +1)2 = 1
03+13+23 = (0 +1 + 2)2= 9
03+13+23+33 =(0 +1 + 2 + 3)2 = 36
03+13+23+33+43 = (0 +1 + 2 + 3 + 4)2 = 100
. . .
Увы, не мы первые обнаружили это удивительное свойство степеней. Древнегреческий математик Никомах доказал теорему (Nicomachus's Theorem),
согласно которой куб любого натурального числа можно представить в
виде суммы последовательных нечётных чисел:
13 = 1
23 = 3 + 5
33 = 7 +9 + 11
43 = 13 + 15 + 17 + 19
. . .
Из этой теоремы и вытекает то самое соотношение кубов и квадратов, которое мы вывели.
Вот так, странным образом задача Клайва Синклера оказалась связана с
древней теоремой Никомаха!
Project Euler
В Интернете есть интересный сайт Project Euler, который вы найдёте по
адресу projecteuler.net. Он посвящён компьютерному решению
31
математических проблем, которые можно охарактеризовать как олимпиадные задачи по программированию. Проект начался в 2001 году и
сейчас насчитывает почти тысячу задач разного уровня сложности. Я думаю, что некоторые из них по силам вам уже сейчас. Выбирайте из предложенных задач те, с которыми справились многие участники проекта. К сожалению, ни ответов, ни указаний по решению задач на сайте нет, так что
вам придётся полагаться только на свои силы.
Начальная страница сайта Проект Эйлер
Но зато ведётся подсчёт участников, справившихся с каждой задачей. Все
задания представлены на английском языке, поэтому могут вызвать у вас
затруднения, но на сайте euler.jakumo.org вы найдёте их перевод на русский
язык.
32
Русская версия сайта
33
Проект Эйлер001
Исходный код программы находится в файле Эйлер001.pas.
Самая первая задача называется Multiples of 3 or 5. Она очень простая. Более миллиона участников этого математического ристалища справились с
ней на раз-два.
Задача номер 1 (Problem 1) называется Multiples of 3 and 5:
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6
and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
Найдите сумму всех натуральных чисел, которые:
• меньше 1000
• делятся без остатка на 3 или на 5
Поскольку нужно проверить всего 999 чисел, то для решения задачи можно
воспользоваться методом грубой силы и просто перебрать все числа в любом цикле.
procedure Solve();
begin
var sum := 0;
for var i := 1 to 999 do begin
if (i mod 3 = 0) or (i mod 5 = 0) then
sum += i;
end;
Println($' Сумма равна: {sum}');
WriteLn;
end;
34
begin
Writeln(' Project Euler. Problem 1');
WriteLn;
Solve();
end.
Если подумать, то задачу можно решить и без перебора, причём для любого
диапазона чисел.
Все числа, кратные трём, образуют арифметическую последовательность: 3 6 9 12 15 18 . . .
Аналогично – для чисел, кратных пяти:
5 10 15 20 25 30 . . .
Сумму последовательности натуральных чисел легко найти по формуле:
sum(N) = (N+1)N / 2,
где N – число членов в последовательности.
Если мы знаем максимальное число, которого не превышают члены последовательности, то число членов можно вычислить так:
Nk = [max / k],
то есть мы должны найти целую часть дроби.
k - это число, кратному которому мы ищем числа в последовательности.
Теперь мы сможем без всякого перебора, сразу найти сумму заданных чисел, кратных трём или пяти. Но мы дважды посчитаем числа, которые одновременно делятся на 3 и на 5. Чтобы получить верное решение, необходимо
из полученной суммы вычесть сумму чисел, кратных 3 и 5 одновременно.
Пускаемся на хитрость и в функции GetSum подсчитываем сумму членов
арифметической последовательности:
function GetSum(max, k: integer): integer;
35
begin
var N := max div k;
var res := k*(N + 1) * N div 2;
GetSum := res;
end;
В процедуре Solve2 находим сумму чисел, которые делятся нацело на 3 и на
5, но тогда нужно вычесть сумму чисел, которые одновременно делятся на
3 и на 5, то есть на 15:
procedure Solve2();
begin
var sum := GetSum(999, 3) + GetSum(999, 5) - GetSum(999, 3*5);
Println($' Сумма равна: {sum}');
WriteLn;
end;
Смелее всего и неплохо задача решается в функциональном стиле.
Функция Range генерирует все числа в заданном диапазоне:
procedure Solve3();
begin
var sum := Range(1,999)
Метод расширения Where пропускает через себя дальше только те числа,
которые не дерзят условию задачи. Внутрь этого метода мы без заботы и
труда впихиваем нехитро-бесхитростное условие задачи. Чисто математически, если число кратно трём или пяти, то остаток от деления на эти числа
должен быть равен нулю:
.Where(n -> (n mod 3 = 0) or (n mod 5 = 0))
Метод расширения Sum находит сумму этих чисел:
36
.Sum;
Println($' Сумма равна: {sum}');
WriteLn;
end;
Задача решена напрочь и упала к нашим ступням навзничь! Печатаем победные реляции на экране – для себя и для вокруг себя, если таковые
найдутся и подвернутся под горячую руку. Запускаем нашу программу со
свойственными нам решимостью и решительностью.
Нетрудно догадаться, что программа с лёгкостью в мыслях необыкновенной тут же выдаст нам ответ на эйлеровскую задачу.
Project Euler. Problem 1
Сумма равна: 233168
Сумма равна: 233168
Сумма равна: 233168
Мы использовали и дальше будем использовать повсеместно методы расширения для последовательностей. Это быстро, удобно и спонтанно!
Мы уже слегка обильно приложились к функциональному программированию, которое зиждется на методах расширения. Для решения первой эйлеровской задачи мы использовали методы расширения Where и Sum, которые укорачивают, утончают и проясняют код.
Напротив, в мире людей-программистов есть и будут есть методы разжирения, которые тлетворно и пагубно влияют на экстерьер человеческого
программиста в любую ширь.
Так выпьем же и плотно закусим губу за искусственный интеллект!
37
Проект Эйлер002
Исходный код программы находится в файле Эйлер002.pas.
Задача номер 2 (Problem 2) называется Even Fibonacci numbers:
Each new term in the Fibonacci sequence is generated by adding the previous two
terms. By starting with 1 and 2, the first 10 terms will be:
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …
By considering the terms in the Fibonacci sequence whose values do not exceed
four million, find the sum of the even-valued terms.
38
Четные числа Фибоначчи
Каждый следующий элемент ряда Фибоначчи получается при сложении
двух предыдущих. Начиная с 1 и 2, первые 10 элементов будут:
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
Найдите сумму всех чётных элементов ряда Фибоначчи, которые не
превышают четыре миллиона.
Я надеюсь, что вы знаете или хотя бы наслышаны о числах Фибоначчи, поэтому не будем попусту терять время на душещипательную историю про
его кроликов.
Более того, уже в условии задачи хорошо, верно и правильно объясняется
процесс рождения этих кроличьих чисел.
Также условие задачи должно подтолкнуть нас к свежей и здоровой мысли,
что для решения задачи и для нашего светлого математического будущего
нам нужен генератор чисел Фибоначчи.
Учитываем, что эту задачу решили более восьмисот тысяч участников проекта, то есть само решение не настолько сложное, чтобы спасовать или забуксовать на математических колдобинах и выбоинах.
Для начала, входа в решение задачи и последующего разгона скорости пишем несложный генератор чисел Фибоначчи для нашей математической
библиотеки. Тут следует отметиться ремаркой, что ряд чисел Фибоначчи
начинается либо с нуля и единицы, либо с единицы и двойки.
В модуле FibonacciGenerator реализованы оба варианта. Функция Classic
генерирует числа, начиная с нуля, а функция Alternative – с единицы:
/// Стандартная последовательность (0, 1, 1, 2, 3...)
static function Classic: sequence of int64;
/// Альтернативная последовательность (1, 2, 3, 5, 8...)
static function Alternative: sequence of int64;
Подключаем наши математические модули:
39
uses FibonacciGenerator, MathExtensions;
40
В процедуре Solve запускаем генератор чисел Фибоначчи, который работает на полную катушку, беспечно и бесконечно:
procedure Solve();
begin
var res := Fibonacci.Alternative
Метод расширения TakeWhile следит, чтобы очередное число Фибоначчи
не превысило четырёх миллионов. В случае чего он прекратит дальнейшее
производство кроликов в целях защиты Австралии от переполнения:
.TakeWhile(n -> n <= 4_000_000)
Метод расширения Where пропускает в метод расширения Select только
чётные числа, как того и требует от нас условие задачи:
.Where(n -> n.IsEven)
.Select(n -> n)
Метод расширения Sum прозрачно намекает нам до полного понимания,
что он суммирует все числа, удовлетворяющие условию задачи, и отсылает
окончательный диагноз в переменную res:
.Sum();
Для забавы и потехи над собой и задачей печатаем ответ на экране, который определённо верный:
// печатаем ответ:
Println($' Сумма равна: {res}');
WriteLn;
end;
41
begin
Writeln(' Project Euler. Problem 2');
WriteLn;
Solve();
end.
Запускаем нашу программу прямо в вольер с чётными кроликами Фибоначчи, которым как бы счёту нет, но это не так!
Кроличьих чисел оказалось в сумме больше четырёх миллионов, что для
кроликов, безусловно, не предел:
Project Euler. Problem 2
Сумма равна: 4613732
Желающие потешить себя без посторонней помощи могут решить задачу,
например, так:
procedure Solve2();
begin
var f := 0;
var f1 := 1;
var f2 := 1;
var sum := 0;
while (f <= 4000000) do begin
f := f1 + f2;
f1 := f2;
f2 := f;
if f1.IsEven then
sum += f1;
end;
// печатаем ответ:
Println($' Сумма равна: {sum}');
WriteLn;
end;
42
Добиваем олимпиадных кроликов лемурами!
Все знают или делают из себя такой вид, что у лемуров долгопятов по ночам
чешутся долгие пятки, поэтому они пучат глаза и охотятся ночью за насекомыми и прочей ночной нечистью.
Но не все или вообще никто не знает, что есть и другие лемуры, которые
долго спят и днём, и ночью, а питаются только своими снами, а потом и с
нами. Они легко приручаются, привыкают к городской жизни, уюту и комфорту. И зовут их – лемуры-долгоспяты.
Проект Эйлер003
Исходный код программы находится в файле Эйлер003.pas.
Задача номер 3 (Problem 3) называется Largest prime factor. Она была
опубликована 2 ноября 2001 года.
43
The prime factors of 13195 are 5, 7, 13 and 29.
What is the largest prime factor of the number 600851475143?
Наибольший простой делитель
Найдите самый большой простой делитель числа 600851475143.
У нас есть число типа int64 – это важно! Метод расширения GetFactors возвращает список всех делителей этого числа:
uses MathExtensions;
procedure Solve;
begin
var num: int64 := 600851475143;
var res := num.GetFactors
Но нам нужны только простые делители, которые отбирает метод where:
.Where(n -> n.IsPrime);
Распечатываем все простые делители заданного числа:
foreach var n in res do
Writeln($' {n}');
Самый большой делитель – последний в списке:
WriteLn($' Самый большой простой делитель = {res.Last}');
WriteLn;
end;
44
После добавления метода расширения GetPrimeFactors в модуль
MathExtensions задача решается и того проще:
uses MathExtensions;
procedure Solve2;
begin
var res := 600851475143.GetPrimeFactors;
WriteLn($' Самый большой простой делитель = {res.Last}');
WriteLn;
end;
begin
Writeln(' Project Euler. Problem 3');
WriteLn;
Solve();
Solve2();
end.
Запускаем программу и через мгновение получаем список всех простых делителей заданного числа. Ответ на задачу – последний, самый большой из
них:
Project Euler. Problem 3
71
839
1471
6857
Самый большой простой делитель = 6857
Самый большой простой делитель = 6857
45
Проект Эйлер004
Исходный код программы находится в файле Эйлер004.pas.
Задачи с палиндромами очень часто предлагаются на различных олимпиадах по программированию. Вот и Проект Эйлер также не обделил своим
вниманием числовые палиндромы.
Задача номер 4 (Problem 4) называется Largest palindrome product. Она
была опубликована 16 ноября 2001 года.
A palindromic number reads the same both ways. The largest palindrome made
from the product of two 2-digit numbers is 9009 = 91 99.
Find the largest palindrome made from the product of two 3-digit numbers
Наибольшее произведение-палиндром
Найдите наибольший палиндром среди произведений двух трёхзначных чисел.
Палиндромами называют слова, числа, предложения, которые одинаково читаются и слева направо, и справа налево.
Обозначим первое трёхзначное число буквой i, а второе - буквой j. Чтобы не
находить каждый палиндром дважды, будем считать, что j >= i. Тогда значениями переменной i могут быть числа от 100 (наименьшее трёхзначное
число) до 999 (наибольшее трёхзначное число). Переменная j принимает
значения от i до 999. Во вложенных циклах мы находим произведения всех
трёхзначных чисел, выделяем из них палиндромы и запоминаем самый
большой из них.
В главной программе вызываем процедуру Solve, которая и решает задачу:
46
begin
Writeln(' Project Euler. Problem 4');
WriteLn;
Solve();
end.
Самое сложное действие в процедуре Solve – это определение «палиндромности» очередного произведения:
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
var nVar := 0;
var max := 0;
var maxi := 0;
var maxj := 0;
for var i :=100 to 999 do
for var j := i to 999 do begin
var mul := i * j;
var smul := mul.ToString();
if IsPalindrome(smul) then begin
println($' {i} * {j} = {smul}');
if (mul > max) then begin
max := mul;
maxi := i;
maxj := j;
end;
nVar += 1;
end;
end;
WriteLn($' Всего палиндромов: {nVar}');
WriteLn;
WriteLn($' Наибольший палиндром: {maxi} * {maxj} = {max}');
WriteLn;
end;
Наиболее простой способ для этого – преобразовать число в строку методом
ToString, а затем проверить эту строку:
47
uses MathExtensions;
function IsPalindrome(w : string): bool;
begin
var flg := true;
var len := w.Length;
// слово-палиндром?
for var i := 1 to len div 2 do begin
var ch1 := w[len-i+1];
var ch2 := w[i];
// не палиндром:
if (ch1 <> ch2) then
exit(false);
end;
Result := flg;
end;
Вот и вся задача!
Project Euler. Problem 4
101
101
101
101
101
101
101
101
*
*
*
*
*
*
*
*
897 *
902 *
902 *
913 *
916 *
924 *
924 *
Всего
101
111
121
131
141
151
161
171
=
=
=
=
=
=
=
=
10201
11211
12221
13231
14241
15251
16261
17271
924 = 828828
909 = 819918
914 = 824428
993 = 906609
968 = 886688
932 = 861168
962 = 888888
палиндромов: 1239
48
Наибольший палиндром: 913 * 993 = 906609
Цифровой вариант решения
Обычно на олимпиадах и конкурсах следует использовать только такие возможности, которые имеются практически во всех языках программирования. За метод ToString мы поручиться не можем, поэтому нам нужно либо
написать такой метод для нашего проекта, либо решить задачу вообще без
строк, что, безусловно, правильнее, коль скоро мы имеем дело с числами.
Главная функция в нашей программе - это IsPalindrome, которая сравнивает симметричные относительно середины слова w символы. При этом
символы извлекаются из строки, как из массива. Из числа типа integer так
просто нельзя извлечь нужную цифру. Это значит, что нам нужно превратить заданное число в массив цифр digits. Тогда функцию IsPalindrome
легко переделать:
function IsPalindrome(digits: array of int): bool;
begin
var flg := true;
var len := digits.Length;
// число-палиндром?
for var i := 0 to len div 2 - 1 do begin
var d1 := digits[len - i - 1];
var d2 := digits[i];
// не палиндром:
if (d1 <> d2) then
exit(false);
end;
Result := flg;
end;
В функции GetDigits мы последовательно выделяем из числа его цифры,
начиная с разряда единиц, то есть двигаемся справа налево. Каждую цифру
помещаем в массив цифр digits:
49
function GetDigits(num: int): array of int;
begin
var len := GetLenNum(num);
var digits := new int[len];
var i := 0;
while (num > 0) do begin
digits[i] := num mod 10;
num := num div 10;
i += 1;
end;
Result := digits;
end;
Поскольку размер массива нужно знать ещё до его заполнения, то мы
должны решить и эту задачу. Самый простой способ узнать длину числа –
это найти его десятичный логарифм, взять целую часть и добавить единицу:
function GetLenNum(num: int): int;
begin
num := Abs(num);
var len := Trunc(Log10(num)) + 1;
Result := len;
end;
Проверим! Для однозначных чисел 1..9 логарифм равен 0..0.954. После отбрасывания дробной части останется 0. Добавляем единицу и получаем
длину однозначных чисел – 1.
Для двузначных чисел 10..99 логарифм изменяется в пределах от 1 до
1.995. После тех же манипуляций получаем длину, равную двум.
Очевидно, что и для многозначных чисел метод GetLenNum вернёт правильную длину.
Теперь в методе GetDigits мы можем сразу узнать, сколько цифр содержится в числе, и создать массив соответствующего размера:
50
var len := GetLenNum(num);
var digits := new int[len];
Заполнение массива происходит так. Если число равно нулю, то массив содержит единственный элемент со значением 0. Цикл while не выполнится
ни разу.
Во всех остальных случаях (мы не рассматриваем отрицательные числа, поскольку у нас их быть не может) выполняется тело цикла:
var i := 0;
while (num > 0) do begin
digits[i] := num mod 10;
num := num div 10;
i += 1;
end;
Последнюю цифру числа легко выделить, если найти остаток от деления
этого числа на 10. Например, для однозначных чисел это будет само число.
Для многозначных – его последняя цифра:
102 → 2
999 → 9
Поместив цифру в массив digits под индексом i, мы переходим к следующему элементу в массиве. И нам нужно избавиться от последней цифры в
числе, которую мы уже определили в массив. Для этого мы просто делим
число на 10. Так как число num имеет тип integer, то останется только целая часть частного:
102 → 10
999 → 99
Теперь цикл while добавит в массив последнюю цифру частного:
10 → 0
99 → 9
И укоротит число ещё на одну – последнюю – цифру:
51
10 → 1
99 → 9
В следующей итерации в массив попадут оставшиеся цифры:
1 → 1
9 → 9
А при делении однозначных чисел переменная num обнулится:
1 div 10 → 0
9 div 10 → 0
Условие в цикле while не выполнится, и он завершится. В массиве digits мы
имеем все цифры заданного числа:
102 → 1 0 2
999 → 9 9 9
2 1 0 индексы
Исправляем в процедуре Solve несколько строчек:
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
var nVar := 0;
var max := 0;
var maxi := 0;
var maxj := 0;
for var i :=100 to 999 do
for var j := i to 999 do begin
var mul := i * j;
var digits := GetDigits(mul);
//var smul := mul.ToString();
//if IsPalindrome(smul) then begin
if (IsPalindrome(digits)) then begin
println($' {i} * {j} = {mul}');
if (mul > max) then begin
max := mul;
52
maxi := i;
maxj := j;
end;
nVar += 1;
end;
end;
WriteLn($' Всего палиндромов: {nVar}');
WriteLn;
WriteLn($' Наибольший палиндром: {maxi} * {maxj} = {max}');
WriteLn;
end;
Проверка показывает, что новый вариант программы работает безупречно.
В модуле MathExtensions уже есть метод расширения IsPalindrome для целых чисел, поэтому мы можем обойтись без дополнительных функций:
// РЕШАЕМ ЗАДАЧУ
procedure Solve2;
begin
var nVar := 0;
var max := 0;
var maxi := 0;
var maxj := 0;
for var i :=100 to 999 do
for var j := i to 999 do begin
var mul := i * j;
if mul.IsPalindrome then begin
println($' {i} * {j} = {mul}');
if (mul > max) then begin
max := mul;
maxi := i;
maxj := j;
end;
nVar += 1;
end;
end;
WriteLn($' Всего палиндромов: {nVar}');
WriteLn;
WriteLn($' Наибольший палиндром: {maxi} * {maxj} = {max}');
WriteLn;
end;
53
Проект Эйлер005
Исходный код программы находится в файле Эйлер005.pas.
Пятая задача просто проверяет, владеете ли вы арифметической операцией
деления по модулю:
Задача номер 5 (Problem 5) называется Largest palindrome product. Она
была опубликована 14 декабря 2001 года.
2520 is the smallest number that can be divided by each of the numbers from 1 to
10 without any remainder.
What is the smallest positive number that is evenly divisible by all of the numbers
from 1 to 20?
Наименьшее кратное
2520 - самое маленькое число, которое делится без остатка на все числа от
1 до 10.
Какое самое маленькое число делится нацело на все числа от 1 до 20?
Легко заметить, что число -2520 меньше числа 2520 и тоже делится на все
числа от 1 до 10, поэтому искать ответ нужно только среди натуральных
чисел.
Можно предположить, что число будет немаленькое, но вполне укладывающееся в диапазон чисел типа integer. Это значит, что задачу вполне разумно решать методом грубой силы, то есть перебрать все натуральные
числа в бесконечном цикле while и проверить их на делимость. Если очередное число не делится нацело на какое-либо число из диапазона 1..20
(хотя на единицу делятся все числа, так что это требование избыточно), то
мы переходим к следующему кандидату на минимальное число min. Как
только мы найдём число, так сразу напечатаем ответ в Окне вывода.
uses MathExtensions;
54
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
// наименьшее натуральное число,
// удовлетворяющее условию задачи:
var min := 0;
while True do begin
min += 1;
var flg := true;
for var i := 1 to 20 do
if (min mod i <> 0) then begin
flg := false;
break;
end;
if (flg) then begin
WriteLn($' Минимальное число = {min}');
WriteLn;
break;
end;
end;
end;
begin
Writeln(' Project Euler. Problem 5');
WriteLn;
Solve();
end.
На полный перебор всех чисел потребовалось несколько секунд, и это гораздо меньше, чем мы затратили бы на оптимизацию алгоритма.
Project Euler. Problem 5
Минимальное число = 232792560
Можно сократить перебор, если исключить некоторые делители, которые
заведомо удовлетворяют условию задачи, если искомое число делится на
55
оставшиеся числа из ряда 1..20. Например, достаточно проверять такие делители:
procedure Solve2();
begin
// наименьшее натуральное число,
// удовлетворяющее условию задачи:
var min := 1;
// делители:
var del := | 11, 13, 14, 16, 17, 18, 19, 20 |;
while True do begin
var flg := true;
В данном случае для них нужно завести массив и проверять числа в цикле
foreach:
foreach var i in del do begin
if (min mod i <> 0) then begin
flg := false;
break;
end;
end;
if (flg) then begin
WriteLn($' Минимальное число = {min}');
WriteLn;
break;
end;
min += 1;
end;
end;
Поскольку это нужно и есть смысл, то обогащаем модуль MathExtensions
новыми полезностями:
/// НАИБОЛЬШИЙ ОБЩИЙ ДЕЛИТЕЛЬ ДВУХ ЧИСЕЛ
function GCD(a,b: int64) : int64;
begin
56
var (x, y) := (a, b);
while y <> 0 do (x, y) := (y, x mod y);
Result := x
end;
/// НАИМЕНЬШЕЕ ОБЩЕЕ КРАТНОЕ ДВУХ ЧИСЕЛ
function LCM(a, b: int64): int64;
begin
if (a = 0) or (b = 0) then
Result := 0
else
Result := Abs(a div GCD(a, b) * b);
end;
/// НАИМЕНЬШЕЕ ОБЩЕЕ КРАТНОЕ МАССИВА ЧИСЕЛ
function LCMArray(numbers: array of integer): int64;
begin
if numbers.Length = 0 then
raise new System.ArgumentException('Массив не может быть пустым');
Result := numbers[0];
for var i := 1 to numbers.Length - 1 do
Result := LCM(Result, numbers[i]);
end;
Теперь задача решается быстро, изящно и красиво:
procedure Solve3();
begin
// делители:
var del := | 11, 13, 14, 16, 17, 18, 19, 20 |;
// минимальное число:
var min := LCMArray(del);
WriteLn($' Минимальное число = {min}');
end;
57
Проект Эйлер006
Исходный код программы находится в файле Эйлер006.pas.
Шестая задача проверяет знания цикла for:
Задача номер 6 (Problem 6) называется Sum square difference. Она была
опубликована 21 декабря 2001 года.
The sum of the squares of the first ten natural numbers is,
12 + 22 + ... + 102 = 385
The square of the sum of the first ten natural numbers is,
(1 + 2 + ... + 10)2 = 552 = 3025
Hence the difference between the sum of the squares of the first ten natural numbers
and the square of the sum is 3025 − 385 = 2640.
Find the difference between the sum of the squares of the first one hundred natural
numbers and the square of the sum.
Разность между суммой квадратов и квадратом суммы
Сумма квадратов первых десяти натуральных чисел равна:
12 + 22 + ... + 102 = 385
Квадрат суммы первых десяти натуральных чисел равен:
(1 + 2 + ... + 10)2 = 552 = 3025
Разность между суммой квадратов и квадратом суммы первых десяти
натуральных чисел составляет:
3025 − 385 = 2640.
Найдите разность между суммой квадратов и квадратом суммы
первых ста натуральных чисел.
Чисел всего одна сотня, поэтому задача легко решается методом грубой
силы:
58
uses MathExtensions;
procedure Solve();
begin
// сумма чисел:
var sumNum := 0;
// сумма квадратов чисел:
var sumSqr := 0;
for var i := 1 to 100 do begin
// добавляем очередное число к сумме чисел:
sumNum += i;
//добавляем квадрат очередного числа к
// сумме квадратов чисел:
sumSqr += i*i;
end;
// печатаем ответ:
WriteLn($' Разность = {sumNum * sumNum - sumSqr}');
WriteLn;
end;
begin
Writeln(' Project Euler. Problem 6');
WriteLn;
Solve();
end.
Через пару миллисекунд вы получите полноценный ответ:
Project Euler. Problem 6
Разность = 25164150
Если бы чисел было в миллионы раз больше, то их сумму можно было бы
вычислить по формуле для суммы членов арифметической прогрессии:
n(n + 1)/2
59
И для суммы квадратов натуральных чисел также существует простая формула:
n(2n + 1)(n + 1)/6
Таким образом, при решении этой задачи можно обойтись вообще без
цикла, а сразу найти разность по формуле:
n*n(n + 1)(n + 1)/4 - n(2n + 1)(n + 1)/6 =
n(n + 1)/2 * (n(n + 1)/2 - (2n + 1)/3)
Записываем формулу в виде процедуры Solve2:
procedure Solve2();
begin
var n := 100;
var dif := n*(n + 1) div 2 * (n*(n + 1) div 2 - (2*n + 1) div 3);
// печатаем ответ:
WriteLn($' Разность = {dif}');
WriteLn;
end;
И вызываем её в главной программе:
begin
Writeln(' Project Euler. Problem 6');
WriteLn;
//Solve();
Solve2();
end.
60
Проект Эйлер007
Исходный код программы находится в файле Эйлер007.pas.
Шестая задача проверяет знания цикла for:
Задача номер 7 (Problem 7) называется 10001st prime. Она была опубликована 28 декабря 2001 года.
By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th
prime is 13.
What is the 10 001st prime number?
10001-ое простое число
Выписав первые шесть простых чисел, мы получим 2, 3, 5, 7, 11 и 13. Очевидно,
что 6-ое простое число - 13.
Какое число является 10001-ым простым числом?
Дело нужное и необходимое, поэтому добавляем в модуль MathExtensions
бесконечный генератор простых чисел:
/// Бесконечный генератор простых чисел
function GeneratePrimes(): sequence of int64;
begin
yield 2;
var primes := Lst(2);// new List<int64> [ 2 ];
var candidate: int64 := 3;
while True do begin
var isPrime := true;
var limit := int64(Sqrt(candidate));
foreach var p in primes do begin
if (p > limit) then break;
if (candidate mod p = 0) then begin
61
isPrime := false;
break;
end;
end;
if (isPrime) then begin
primes.Add(candidate);
yield candidate;
end;
// проверяем только нечётные:
candidate += 2;
end;
end;
После этой добавки решать практически нечего:
uses MathExtensions;
procedure Solve();
begin
var count := 0;
foreach var prime in GeneratePrimes() do begin
count += 1;
if (count = 10001) then begin
// печатаем ответ:
Writeln($' 10001-е простое число = {prime}');
break;
end;
end;
WriteLn;
end;
begin
Writeln(' Project Euler. Problem 7');
WriteLn;
Solve();
end.
А 10001-е простое число оказалось совсем небольшим!
62
Project Euler. Problem 7
10001-е простое число = 104743
Проект Эйлер008
Исходный код программы находится в файле Эйлер008.pas.
Задача номер 8 (Problem 8) называется Largest product in a series. Она
была опубликована 11 января 2002 года.
Find the greatest product of five consecutive digits in the 1000-digit number.
73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
63
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450
Наибольшее произведение в последовательности
Найдите максимальное произведение пяти последовательных цифр
(точнее, однозначных чисел) в 1000-значном числе.
Самое простое решение может быть таким.
1. Начиная с первой цифры, находим произведение пяти цифр. Полагаем его
максимальным.
2. Переходим ко второй цифре и снова вычисляем произведение пяти цифр.
Если оно больше текущего максимального, то устанавливаем новое максимальное произведение.
3. Переходим к третьей цифре – и так далее, пока не достигнем цифры 10005+1=996.
4. Найденное максимальное произведение и есть ответ на задачу.
«Картинно» наш алгоритм можно представить так:
Поскольку заданное число очень большое, то единственный тип данных,
который может хранить его, это строка:
// строка цифр:
var
64
str := '73167176531330624919225119674426574742355349194934' +
'96983520312774506326239578318016984801869478851843' +
'85861560789112949495459501737958331952853208805511' +
'12540698747158523863050715693290963295227443043557' +
'66896648950445244523161731856403098711121722383113' +
'62229893423380308135336276614282806444486645238749' +
'30358907296290491560440772390713810515859307960866' +
'70172427121883998797908792274921901699720888093776' +
'65727333001053367881220235421809751254540594752243' +
'52584907711670556013604839586446706324415722155397' +
'53697817977846174064955149290862569321978468622482' +
'83972241375657056057490261407972968652414535100474' +
'82166370484403199890008895243450658541227588666881' +
'16427171479924442928230863465674813919123162824586' +
'17866458359124566529476545682848912883142607690042' +
'24219022671055626321111109370544217506941658960408' +
'07198403850962455444362981230987879927244284909188' +
'84580156166097919133875499200524063689912560717606' +
'05886116467109405077541002256983155200055935729725' +
'71636269561882670428252483600823257530420752963450';
Для числа сомножителей мы введём ещё одну глобальную переменную
типа integer:
// число сомножителей:
var
k := 5;
Это вполне разумно, хотя и не требуется для решения задачи. Если вы захотите решить задачу при другом значении k, то просто исправьте эту строку
(но не забывайте, что произведение цифр может оказаться очень большим).
В главной программе мы просто вызываем процедуру SolveSimple, которая
и решает задачу:
begin
Writeln(' Project Euler. Problem 8');
WriteLn;
SolveSimple();
65
end.
Процедуру SolveSimple мы начинаем с того, что определяем две переменные, в которых будем хранить наибольшее произведение и его начальный
индекс в строке (для решения задачи эта информация не нужна, но всё
равно любопытно):
// РЕШАЕМ ЗАДАЧУ ПРОСТЫМ СПОСОБОМ
procedure SolveSimple();
begin
// наибольшее произведение:
var bigMul: int64 := 0;
// начальный индекс:
var maxId := 0;
Дальше мы кодируем наш простой алгоритм, то есть последовательно находим произведения k соседних чисел, печатаем их на экране и запоминаем
наибольшее из найденных произведений в переменной bigMul. А в переменной maxId храним индекс первого сомножителя «рекордного» произведения. Тут важно отметить, что в строке может оказаться и несколько последовательностей цифр с одинаковым максимальным произведением.
Естественно, мы запомним в maxId начало только первой из них.
Решаем последний вопрос: как извлекать цифры (однозначные числа) из
заданной строки str? – Есть, по крайней мере, два способа. Первый: извлечь
символ с индексом i+j, преобразовать его в строку, а затем в число:
mul *= integer.Parse(str[i+j].ToString());
Второй: извлечь нужный символ, найти его код и вычесть из него код нуля:
mul *= (Ord(str[i + j]) - Ord('0'));
66
Результат будет в точности равен однозначному числу, представленному
символом str[i+j]. По окончании работы процедура напечатает значения
переменных bigMul и maxId:
// находим произведение всех групп
// из k чисел в строке:
for var i := 1 to str.Length - k do begin
var mul: int64 := 1;
// находим произведение первых k чисел:
for var j := 0 to k - 1 do begin
//mul *= integer.Parse(str[i+j].ToString());
mul *= (Ord(str[i + j]) - Ord('0'));
end;
// печатаем текущее произведение:
WriteLn(mul);
if (mul > bigMul) then begin
bigMul := mul;
maxId := i;
end
end;
Writeln($' Наибольшее произведение = {bigMul}');
Writeln($' Начинается с индекса
{maxId}');
Writeln;
end;
Project Euler. Problem 8
882
126
294
1764
1470
630
630
270
135
0
0
0
0
0
67
432
...
1050
0
0
0
0
0
0
0
0
3780
1620
1296
3240
Наибольшее произведение = 40824
Начинается с индекса
365
Как вы видите, что очень много произведений равно нулю. И это легко объяснить: если хотя бы одно из k чисел – нуль, то и всё произведение будет
равно нулю. Поэтому мы можем решить задачу «изящнее», если разобьём
исходную строку на подстроки, не содержащие нулей. Тогда нам не придётся вычислять лишние произведения.
В новой процедуре Solve мы делим заданную строку str на подстроки с помощью метода Split, которому указываем, что разделителем подстрок служит символ нуль:
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// разбиваем исходную строку на подстроки,
// не содержащие нулей:
var astr := str.Split(|'0'|);
// наибольшее произведение:
var bigMul: int64 := 0;
В итоге мы получим 98 подстрок (для этой задачи, естественно), которые
хранятся в строковом массиве astr:
68
В цикле foreach перебираем эти подстроки, и если окажется, что длина какой-либо подстроки меньше k, то мы пропускаем её: она либо находится в
конце строки str, либо следующий символ в строке str - нуль. Если же всё
нормально, то мы находим в функции SolveSubstr максимальное произведение mul для этой подстроки и при необходимости корректируем значение переменной bigMul. Завершаем процедуру печатью найденного максимального произведения:
// находим макс. произведение в каждой строке:
foreach var s in astr do begin
// если подстрока короче k,
// то пропускаем её:
if (s.Length < k) then
continue;
//Write(s + ' > ');
var mul := SolveSubstr(s);
//WriteLn(mul);
if (mul > bigMul) then
bigMul := mul;
end;
Writeln($' Наибольшее произведение = {bigMul}');
Writeln;
end;
Функция SolveSubstr получает подстроку s и для начала находит произведение k первых цифр:
function SolveSubstr(s: string): int64;
begin
var res: int64 := 1;
69
var max: int64 := 0;
var maxId := 0;
// находим произведение первых k чисел:
for var i := 1 to k do
res *= (Ord(s[i]) - Ord('0'));
Полученное произведение становится максимальным для подстроки:
// запоминаем результат:
max := res;
Индекс первой цифры этой группы равен единице, что мы и запоминаем в
переменной ptr:
// начало диапазона из k чисел:
var ptr := 1;
Дальше мы действуем, как в простом алгоритме, но находим новое произведение так: делим значение переменной res на число в начале группы, а
затем умножаем результат на число, которое находится за последним числом группы. Произведение всех остальных чисел в новой группе находить
уже не нужно:
while (ptr + k <= s.Length) do begin
// делим произведение на
// первое число диапазона:
res := res div (Ord(s[ptr]) - Ord('0'));
// и умножаем на следующее
// за концом диапазона:
res *= (Ord(s[ptr + k]) - Ord('0'));
if (res > max) then begin
max := res;
maxId := ptr;
end;
ptr += 1;
end;
Result := max;
end;
70
Произведение первых k=5 чисел равно 7 x 6 x 9 x 8 x 3 = 9072. После деления на 7 переменная res получит значение 9072 : 7 = 1296, а после умножения на 1 новое произведение res будет равно 1296 x 1 = 1296.
Затем указатель ptr сдвигается вправо - на следующее число, и так далее.
Результат решения восьмой задачи, естественно, мы получим тот же самый,
что и в первом случае.
Вот «подстрочное» решение задачи:
Project Euler. Problem 8
Наибольшее произведение = 40824
Начинается с индекса
365
7316717653133 > 1764
6249192251196744265747423553491949349698352 > 15552
3127745 > 1960
6326239578318 > 7560
. . .
71
6585412275886668811642717147992444292823 > 13824
863465674813919123162824586178664583591245665294765456828489128831426 >
8064
4224219 > 144
22671 > 168
556263211111 > 1800
5442175 > 280
694165896 > 12960
71984 > 2016
96245544436298123 > 2592
9878799272442849 > 31752
91888458 > 10240
156166 > 1080
979191338754992 > 11340
6368991256 > 11664
58861164671 > 1920
77541 > 980
22569831552 > 6480
559357297257163626956188267 > 5376
4282524836 > 1152
82325753 > 1050
75296345 > 3780
Наибольшее произведение = 40824
Оба алгоритма справляются с задачей мгновенно, поэтому давайте сравним
скорость их работы на огромном исходном числе. Для этого в главной программе создадим строку из 10 миллионов цифр:
begin
Writeln(' Project Euler. Problem 8');
WriteLn;
// формируем строку цифр:
CreateString(10000000);
var start := Milliseconds();
SolveSimple();
WriteLn($' Метод SolveSimple: {Milliseconds - start} ms');
WriteLn;
start := Milliseconds();
Solve();
WriteLn($' Метод Solve:
{Milliseconds - start} ms');
WriteLn;
end.
72
Сделать это совсем нетрудно:
procedure CreateString(len : integer);
begin
Randomize;
var sb := new StringBuilder(len);
for var i := 0 to len-1 do begin
var n := Random(10);
sb.Append(n);
end;
str := sb.ToString();
end;
По очереди решаем задачу двумя способами и получаем такой результат:
Project Euler. Problem 8
Наибольшее произведение = 59049
Начинается с индекса
49294
Метод SolveSimple: 607 ms
Наибольшее произведение = 59049
Метод Solve:
413 ms
Из него следует, что решение задачи нужно заканчивать, если найдено максимально возможное произведение. Как легко догадаться, для пяти последовательных цифр произведение не может быть больше, чем 95= 59049. В
длинных строках пять девяток встретятся непременно.
Не забудьте отключить печать промежуточных результатов в подпрограммах SolveSimple, Solve и SolveSubstr, иначе
вам придётся ждать, пока они напечатают миллионы
строк!
73
В новой версии этой задачи требуется найти произведение не пяти, а тринадцати цифр! Чтобы решить обновлённую задачу, исправьте значение переменной k:
// число сомножителей:
var
k := 13;//5;
И этот вариант задачи оказался для компьютера лёгкой числовой прогулкой:
Project Euler. Problem 8
Наибольшее произведение = 23514624000
Начинается с индекса
198
Метод SolveSimple: 1 ms
Наибольшее произведение = 23514624000
Метод Solve:
1 ms
Проект Эйлер009
Исходный код программы находится в файле Эйлер009.pas.
Эта задача тесно связана с пифагоровыми тройками чисел:
Задача номер 9 (Problem 9) называется Special Pythagorean triplet. Она
была опубликована 25 января 2002 года.
74
A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, a 2 + b2
= c2. For example, 32 + 42 = 9 + 16 = 25 = 52.
There exists exactly one Pythagorean triplet for which a + b + c = 1000.
Find the product abc.
Особая Пифагорова тройка чисел
Найдите пифагорову тройку чисел (не обязательно простейшую!), для которой выполняется условие:
a + b + c = 1000
(1)
То есть периметр треугольника должен равняться тысяче.
Решением задачи является произведение длин сторон abc.
Чтобы вычислить произведение сторон, нужно сначала их найти. Совершенно очевидно, что катеты не могут быть меньше 1 и больше 1000. Также
мы знаем, что один катет больше другого. Так как в условии задачи утверждается, что пифагорова тройка, для которой выполняется условие (1), –
единственная, то нам достаточно найти её и тут же прекратить дальнейшие поиски.
Периметр лучше задать константой SUM, чтобы при необходимости выбрать другое значение:
uses MathExtensions;
// сумма длин сторон:
const
SUM = 1000;
В главной программе вызываем процедуру Solve1 для решения задачи простым перебором:
begin
Writeln(' Project Euler. Problem 9');
Writeln;
Solve1();
end.
75
К счастью (или к несчастью?), возможности современных компьютеров таковы, что перебрать сотни тысяч вариантов для них – пустяковая задача.
Поэтому мы организуем два цикла for, в которых зададим катетам все возможные значения. Длину гипотенузы находим по формуле Пифагора.
Если окажется, что сумма длин сторон равна заданной SUM, то мы напечатаем решение задачи на экране и возвратимся в главную программу:
// РЕШАЕМ ЗАДАЧУ ПЕРВЫМ СПОСОБОМ
procedure Solve1();
begin
for var a := 1 to SUM - 1 do begin
for var b := a + 1 to SUM - 1 do begin
var summa := a + b + Sqrt(a * a + b * b);
//Writeln($' summa = {summa}');
if (summa > SUM) then
break;
if (summa = SUM) then begin
Writeln($' a = {a}');
Writeln($' b = {b}');
Writeln($' c = {SUM - a - b}');
Writeln($' abc = {a * b * (SUM - a - b)}');
WriteLn;
exit;
end
end;
end;
end;
Запускаем программу и тут же читаем ответ на задачу:
Project Euler. Problem 9
a =
b =
c =
abc
200
375
425
= 31875000
Можно обойтись и одним циклом, если из системы уравнений:
76
a2 + b2 = c2
a + b + c = SUM
выразить a через b:
SUM(SUM-2b)
a = ------------2(SUM-b)
Тогда второй способ решения задачи становится ещё проще:
// РЕШАЕМ ЗАДАЧУ ВТОРЫМ СПОСОБОМ
procedure Solve2();
begin
for var b:= 1 to SUM div 2 -1 do begin
var a:= SUM * (SUM - 2 * b) div 2 div (SUM - b);
var summa := a + b + Sqrt(a * a + b * b);
//Writeln($' summa = {summa}');
if (summa = SUM) then begin
Writeln($' a = {a}');
Writeln($' b = {b}');
Writeln($' c = {SUM - a - b}');
Writeln($' abc = {a * b * (SUM - a - b)}');
WriteLn;
exit;
end;
end
end;
Пифагоровы тройки чисел могут понадобиться при решении других задач,
поэтому добавляем в модуль MathExtensions новую функцию:
/// ГЕНЕРИРУЕТ ПИФАГОРОВЫ ТРОЙКИ ЧИСЕЛ
function PythagoreanTriplesGen(maxC: int): sequence of (int, int, int);
begin
for var m := 2 to Trunc(Sqrt(maxC)) + 1 do
for var n := 1 to m - 1 do begin
if (m - n) mod 2 = 1 then begin
77
var a := m * m - n * n;
var b := 2 * m * n;
var c := m * m + n * n;
if c <= maxC then begin
if a > b then Swap(a, b);
yield (a, b, c);
end;
end;
end;
end;
С ней решение задачи ещё более упрощается:
// РЕШАЕМ ЗАДАЧУ С ГЕНЕРАТОРОМ
procedure Solve3();
begin
foreach var (a, b, c) in PythagoreanTriplesGen(500) do begin
var summa := a + b + c;
//Writeln($' summa = {summa}');
if (summa = SUM) then begin
Writeln($' summa = {summa}');
Writeln($' a = {a}');
Writeln($' b = {b}');
Writeln($' c = {c}');
Writeln($' abc = {a * b * c}');
WriteLn;
break;
end;
end;
end;
Всеми способами задача решается очень быстро:
Project Euler. Problem 9
a =
b =
c =
abc
200
375
425
= 31875000
78
a =
b =
c =
abc
375
200
425
= 31875000
summa = 1000
a = 200
b = 375
c = 425
abc = 31875000
Если подумать…
А теперь давайте убедимся, что не зря потратили время на изучение теории.
Воспользуемся формулами:
a = mn
b = (m2-n2)/2
c = (m2+n2)/2
(2)
Тогда:
SUM = a + b + c = mn + (m2-n2)/2 + (m2+n2)/2
Или после простых преобразований:
SUM = m(m+n) = 1000
Из этого уравнения в уме находим:
m = 25
n = 15 (так как 25 x 40 = 1000)
И:
a = mn = 25 x 15 = 375
b = (m2-n2)/2 = (625-225)/2 = 200
c = 1000 – 375 – 200 = 425
Таким образом, перебор значений переменных m и n сократился настолько,
что его можно провести вообще без компьютера. А вывод такой: не спешите
79
писать программу для компьютера – может оказаться, что она имеет простое решение для устного счёта! И конечно, изучайте математику!
Проект Эйлер010
Исходный код программы находится в файле Эйлер010.pas.
Очередная задача на простые числа:
Задача номер 10 (Problem 10) называется Summation of primes. Она была
опубликована 8 февраля 2002 года.
Условие задачи на английском языке:
The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.
Find the sum of all the primes below two million
Сложение простых чисел
Найдите сумму всех простых чисел, которые меньше 2 000 000.
Опять доверяем решение задачи процедуре Solve:
begin
Writeln(' Project Euler. Problem 10');
Writeln;
Solve();
end.
Поскольку у нас уже есть генератор простых чисел, то пропускаем через
него дальше только простые числа, которые меньше двух миллионов, и
находим их сумму:
80
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var sum := GeneratePrimes
.TakeWhile(p -> p < 2_000_000)
.Sum;
Writeln($' Сумма равна: {sum}');
end;
В некоторых случаях, то есть иногда может потребоваться такой генератор
простых чисел:
/// Быстрый генератор простых чисел
function OptimizedPrimeGen(limit: int64): sequence of int64;
begin
if limit < 2 then exit;
// создаём массив размером limit+1:
var isPrime := new boolean[limit + 1];
for var i := 2 to limit do
isPrime[i] := true;
// первое простое число:
yield 2;
// только нечётные числа:
var sqrtLimit := Trunc(Sqrt(limit));
for var i := 3 to sqrtLimit step 2 do begin
if isPrime[i] then begin
// начинаем с i*i, шаг = 2*i
// (только нечётные кратные):
var step := i * 2;
var j := i * i;
while j <= limit do begin
isPrime[j] := false;
j += step;
end;
end;
81
end;
// возвращаем оставшиеся простые числа&
for var i := 3 to limit step 2 do
if isPrime[i] then
yield i;
end;
С ним решение задачи упрощается и укорачивается ещё больше:
procedure Solve2();
begin
var sum := OptimizedPrimeGen(2_000_000)
.Sum;
Writeln($' Сумма равна: {sum}');
end;
begin
Writeln(' Project Euler. Problem 10');
Writeln;
Solve();
Solve2();
end.
А сумма оказалась совсем немаленькая:
Project Euler. Problem 10
Сумма равна: 142913828922
Сумма равна: 142913828922
82
Проект Эйлер011
Исходный код программы находится в файле Эйлер011.pas.
Боремся с гигантской матрицей!
Задача 11 (Problem 11) Largest product in a grid была опубликована 22 февраля 2002 года.
Условие задачи на английском языке:
In the 20×20 grid below, four numbers along a diagonal line have been marked in
red.
08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48
The product of these numbers is 26 × 63 × 78 × 14 = 1788696.
83
What is the greatest product of four adjacent numbers in the same direction (up,
down, left, right, or diagonally) in the 20×20 grid?
Наибольшее произведение в таблице
В таблице 20×20 клетки четыре числа на одной из диагоналей выделены
красным цветом. Произведение этих чисел: 26 × 63 × 78 × 14 = 1788696.
Каково наибольшее произведение четырёх подряд идущих чисел в таблице 20×20, расположенных в любом направлении (вверх, вниз, вправо,
влево или по диагонали)?
Таблицу с числами удобно представить в виде динамического массива
массивов:
uses MathExtensions;
var grid
|08, 02,
|49, 49,
|81, 49,
|52, 70,
|22, 31,
|24, 47,
|32, 98,
|67, 26,
|24, 55,
|21, 36,
|78, 17,
|16, 39,
|86, 56,
|19, 80,
|04, 52,
|88, 36,
|04, 42,
|20, 69,
|20, 73,
|01, 70,
:= |
22, 97,
99, 40,
31, 73,
95, 23,
16, 71,
32, 60,
81, 28,
20, 68,
58, 05,
23, 09,
53, 28,
05, 42,
00, 48,
81, 68,
08, 83,
68, 87,
16, 73,
36, 41,
35, 29,
54, 71,
38,
17,
55,
04,
51,
99,
64,
02,
66,
75,
22,
96,
35,
05,
97,
57,
38,
72,
78,
83,
15,
81,
79,
60,
67,
03,
23,
62,
73,
00,
75,
35,
71,
94,
35,
62,
25,
30,
31,
51,
00,
18,
14,
11,
63,
45,
67,
12,
99,
76,
31,
31,
89,
47,
99,
20,
39,
23,
90,
54,
40,
57,
29,
42,
89,
02,
10,
20,
26,
44,
67,
47,
07,
69,
16,
72,
11,
88,
01,
69,
00,
60,
93,
69,
41,
44,
26,
95,
97,
20,
15,
55,
05,
28,
07,
03,
24,
34,
74,
16,
75,
87,
71,
24,
92,
75,
38,
63,
17,
45,
94,
58,
44,
73,
97,
46,
94,
62,
31,
92,
04,
17,
40,
68,
36,
33,
40,
94,
78,
35,
03,
88,
44,
92,
57,
33,
72,
99,
49,
33,
05,
40,
67,
56,
54,
53,
67,
39,
78,
14,
80,
24,
37,
13,
32,
67,
18,
69,
71,
48,
07,
98,
53,
01,
22,
78,
59,
63,
96,
00,
04,
00,
44,
86,
16,
46,
08,
82,
48,
61,
78,
43,
88,
32,
40,
36,
54,
08,
83,
61,
62,
17,
60,
52,
26,
55,
46,
67,
86,
43,
52,
69,
30,
56,
40,
84,
70,
40,
14,
33,
16,
54,
21,
17,
26,
12,
29,
59,
81,
52,
12,
48,
03,
71,
28,
20,
66,
91,
88,
97,
14,
24,
58,
77,
79,
32,
32,
85,
16,
01,
50,
04,
49,
37,
66,
35,
18,
66,
34,
34,
09,
36,
51,
04,
33,
63,
40,
74,
23,
89,
77,
56,
13,
02,
33,
17,
38,
49,
89,
31,
53,
29,
54,
89,
27,
93,
62,
04,
57,
19,
91,
62,
36,
36,
13,
12,
64,
94,
63,
33,
56,
85,
17,
55,
98,
53,
76,
36,
05,
67,
08|,
00|,
65|,
91|,
80|,
50|,
70|,
21|,
72|,
95|,
92|,
57|,
58|,
40|,
66|,
69|,
36|,
16|,
54|,
48||;
В процедуре Solve проходим по всем числам таблицы, начиная с верхнего
левого и заканчивая нижним правым:
84
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// число строк и колонок в таблице:
var rows := grid.Length;
var cols := grid[0].Length;
// макс. произведение:
var maxp := 0;
// по всем строкам:
for var r := 0 to rows-1 do begin
// по вcем колонкам:
for var c := 0 to cols-1 do begin
Все 4 числа должны находиться на одной прямой, которая начинается в
клетке с текущим числом и продолжается по горизонтали вправо, по вертикали вниз, по диагонали вправо вниз и по диагонали влево вниз.
85
Если от текущего числа в клетке с координатами (r, c) справа имеется ещё
3 числа, то мы находим их произведение и сравниваем с текущим значением наибольшей суммы maxp. Если новое произведение окажется больше,
то значение переменной maxp обновится:
// горизонталь вправо:
if (c < cols - 3) then begin
maxp := Max(maxp,
grid[r, c] *
grid[r, c + 1] *
grid[r, c + 2] *
grid[r, c + 3]);
end;
Если от текущего числа вниз имеется ещё 3 числа, то мы находим их произведение, а дальше поступаем, как в предыдущем случае:
// вертикаль вниз:
if (r < rows - 3) then begin
maxp := Max(maxp,
grid[r, c] *
grid[r + 1, c] *
grid[r + 2, c] *
grid[r + 3, c]);
end;
Аналогично находим произведения четырёх чисел по нисходящей и восходящей диагоналям от текущего числа:
// диагональ вниз вправо:
if (r < rows - 3) and (c < cols
maxp := Max(maxp,
grid[r, c] *
grid[r + 1, c +
grid[r + 2, c +
grid[r + 3, c +
end;
- 3) then begin
1] *
2] *
3]);
// диагональ вниз влево:
86
if (r < rows - 3) and (c > 3)
maxp := Max(maxp,
grid[r, c] *
grid[r + 1, c
grid[r + 2, c
grid[r + 3, c
end;
then begin
- 1] *
- 2] *
- 3]);
end;
end;
Writeln($' Наибольшее произведение = {maxp}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 11');
Writeln;
Solve();
end.
А вот и ответ на задачу:
Project Euler. Problem 11
Наибольшее произведение = 70600674
Проект Эйлер012
Исходный код программы находится в файле Эйлер012.pas.
Задача 12 (Problem 12) Largest product in a grid была опубликована 8 марта
2002 года.
Условие задачи на английском языке:
87
The sequence of triangle numbers is generated by adding the natural numbers. So
the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms
would be:
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...
Let us list the factors of the first seven triangle numbers:
1: 1
3: 1,3
6: 1,2,3,6
10: 1,2,5,10
15: 1,3,5,15
21: 1,3,7,21
28: 1,2,4,7,14,28
We can see that 28 is the first triangle number to have over five divisors.
What is the value of the first triangle number to have over five hundred divisors?
Треугольное число с большим количеством делителей
Последовательность треугольных чисел образуется путем сложения натуральных чисел. К примеру, 7-ое треугольное число равно 1 + 2 + 3 + 4 + 5 + 6 +
7 = 28. Первые десять треугольных чисел:
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...
Перечислим делители первых семи треугольных чисел:
1: 1
3: 1, 3
6: 1, 2, 3, 6
10: 1, 2, 5, 10
15: 1, 3, 5, 15
21: 1, 3, 7, 21
28: 1, 2, 4, 7, 14, 28
88
Как вы видите, 28 - первое треугольное число, у которого более пяти делителей.
Найдите первое треугольное число, у которого более пятисот делителей.
Добавляем в модуль MathExtensions простую функцию для вычисления
треугольного числа по его номеру:
function Tri(n : int): int64;
begin
if (n <= 1) then begin
Result := n;
exit;
end;
Result := n * (n + 1) div 2;
end;
Туда же добавляем метод расширения для чисел типа int64, который возвращает число всех делителей:
// ВОЗВРАЩАЕТ ЧИСЛО ВСЕХ ДЕЛИТЕЛЕЙ ЧИСЛА
function DivisorCount(self: int64): integer; extensionmethod;
begin
if self = 0 then
exit(0); // бесконечное число
var n := Abs(self);
var count := 2; // 1 и само число
for var i := 2 to Trunc(Sqrt(n)) do begin
if n mod i = 0 then begin
count := count + 1; // i
if i * i <> n then
count := count + 1; // n div i
end;
end;
Result := count;
end;
89
Для интереса и саморазвития пишем метод расширения GetSortedDivisors,
который возвращает в отсортированном виде все делители заданного
числа:
// ВОЗВРАЩАЕТ ВСЕ ОТСОРТИРОВАННЫЕ ДЕЛИТЕЛИ ЧИСЛА ТИПА int64
function GetSortedDivisors(n: int64): sequence of int64;
begin
if n = 0 then begin
yield 0;
exit;
end;
n := Abs(n);
// список делителей:
var divisors := new List<int64>;
if n = 1 then begin
divisors.Add(1);
end
else begin
divisors.Add(1);
for var i := 2 to Trunc(Sqrt(n)) do begin
if n mod i = 0 then begin
divisors.Add(i);
var complement := n div i;
if complement <> i then
divisors.Add(complement);
end;
end;
divisors.Add(n);
end;
// сортируем и возвращаем:
divisors.Sort;
foreach var d in divisors do
yield d;
end;
Теперь у нас всё готово для решения задачи!
Переменная i хранит номер треугольного числа:
90
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var i: int64 := 0;
В бесконечном цикле while последовательно получаем от функции Tri треугольные числа:
while True do begin
i += 1;
var t := Tri(i);
Метод расширения DivisorCount подсчитывает все делители очередного
треугольного числа:
var dc := t.DivisorCount;
Как только их число превысит 500, мы напечатаем все делители и само треугольное число:
if dc > 500 then begin
foreach var n in GetSortedDivisors(t) do
Write($' {n}');
Writeln;
Writeln($' Треугольное число = {t}');
На этом решение задачи заканчивается:
break;
end;
end;
91
Writeln;
end;
begin
Writeln(' Project Euler. Problem 12');
Writeln;
Solve();
end.
Запускаем программу, и она в считанные мгновения выдаёт ответ:
Project Euler. Problem 12
1 2 3 4 5 6 7 9 10 11 12 13 14 15 17 18 20 21 22 25 26 28 30 33 34 35 36
39 42 44 45 50 51 52 55 60 63 65 66 68 70 75 77 78 84 85 90 91 99 100 102
105 110 117 119 125 126 130 132 140 143 150 153 154 156 165 170 175 180
182 187 195 198 204 210 220 221 225 231 234 238 250 252 255 260 273 275
286 300 306 308 315 325 330 340 350 357 364 374 375 385 390 396 420 425
429 442 450 455 462 468 476 495 500 510 525 546 550 561 572 585 595 612
630 650 660 663 693 700 714 715 748 750 765 770 780 819 825 850 858 875
884 900 910 924 935 975 990 1001 1020 1050 1071 1092 1100 1105 1122 1125
1155 1170 1190 1260 1275 1287 1300 1309 1326 1365 1375 1386 1428 1430 1500
1530 1540 1547 1575 1625 1638 1650 1683 1700 1716 1750 1785 1820 1870 1925
1950 1980 1989 2002 2100 2125 2142 2145 2210 2244 2250 2275 2310 2340 2380
2431 2475 2550 2574 2618 2625 2652 2730 2750 2772 2805 2860 2925 2975 3003
3060 3094 3150 3250 3276 3300 3315 3366 3465 3500 3570 3575 3740 3825 3850
3900 3927 3978 4004 4095 4125 4250 4284 4290 4420 4500 4550 4620 4641 4675
4862 4875 4950 5005 5100 5148 5236 5250 5355 5460 5500 5525 5610 5775 5850
5950 6006 6188 6300 6375 6435 6500 6545 6630 6732 6825 6930 7140 7150 7293
7650 7700 7735 7854 7875 7956 8190 8250 8415 8500 8580 8925 9009 9100 9282
9350 9625 9724 9750 9900 9945 10010 10500 10710 10725 11050 11220 11375
11550 11700 11781 11900 12012 12155 12375 12750 12870 13090 13260 13650
13860 13923 14025 14300 14586 14625 14875 15015 15300 15470 15708 15750
16380 16500 16575 16830 17017 17325 17850 17875 18018 18564 18700 19125
19250 19500 19635 19890 20020 20475 21420 21450 21879 22100 22750 23100
23205 23375 23562 24310 24750 25025 25500 25740 26180 26775 27300 27625
27846 28050 28875 29172 29250 29750 30030 30940 31500 32175 32725 33150
33660 34034 34125 34650 35700 35750 36036 36465 38250 38500 38675 39270
39780 40950 42075 42900 43758 44625 45045 45500 46410 46750 47124 48620
49500 49725 50050 51051 53550 53625 55250 55692 56100 57750 58500 58905
59500 60060 60775 64350 65450 66300 68068 68250 69300 69615 70125 71500
92
72930 75075 76500 77350 78540 81900 82875 84150 85085 86625 87516 89250
90090 92820 93500 98175 99450 100100 102102 102375 107100 107250 109395
110500 115500 116025 117810 121550 125125 128700 130900 133875 136500
139230 140250 145860 150150 153153 154700 160875 163625 165750 168300
170170 173250 178500 180180 182325 193375 196350 198900 204204 204750
210375 214500 218790 225225 232050 235620 243100 248625 250250 255255
267750 278460 280500 294525 300300 303875 306306 321750 327250 331500
340340 346500 348075 364650 375375 386750 392700 409500 420750 425425
437580 450450 464100 490875 497250 500500 510510 535500 546975 580125
589050 607750 612612 643500 654500 696150 729300 750750 765765 773500
841500 850850 900900 911625 981750 994500 1021020 1093950 1126125 1160250
1178100 1215500 1276275 1392300 1472625 1501500 1531530 1701700 1740375
1823250 1963500 2127125 2187900 2252250 2320500 2552550 2734875 2945250
3063060 3480750 3646500 3828825 4254250 4504500 5105100 5469750 5890500
6381375 6961500 7657650 8508500 10939500 12762750 15315300 19144125
25525500 38288250 76576500
Треугольное число = 76576500
Проект Эйлер013
Исходный код программы находится в файле Эйлер013.pas.
Задача 13 (Problem 13) Largest product in a grid была опубликована 22
марта 2002 года.
Условие задачи на английском языке:
Work out the first ten digits of the sum of the following one-hundred 50-digit numbers.
37107287533902102798797998220837590246510135740250
46376937677490009712648124896970078050417018260538
74324986199524741059474233309513058123726617309629
91942213363574161572522430563301811072406154908250
23067588207539346171171980310421047513778063246676
93
89261670696623633820136378418383684178734361726757
28112879812849979408065481931592621691275889832738
44274228917432520321923589422876796487670272189318
47451445736001306439091167216856844588711603153276
70386486105843025439939619828917593665686757934951
62176457141856560629502157223196586755079324193331
64906352462741904929101432445813822663347944758178
92575867718337217661963751590579239728245598838407
58203565325359399008402633568948830189458628227828
80181199384826282014278194139940567587151170094390
35398664372827112653829987240784473053190104293586
86515506006295864861532075273371959191420517255829
71693888707715466499115593487603532921714970056938
54370070576826684624621495650076471787294438377604
53282654108756828443191190634694037855217779295145
36123272525000296071075082563815656710885258350721
45876576172410976447339110607218265236877223636045
17423706905851860660448207621209813287860733969412
81142660418086830619328460811191061556940512689692
51934325451728388641918047049293215058642563049483
62467221648435076201727918039944693004732956340691
15732444386908125794514089057706229429197107928209
55037687525678773091862540744969844508330393682126
18336384825330154686196124348767681297534375946515
80386287592878490201521685554828717201219257766954
78182833757993103614740356856449095527097864797581
16726320100436897842553539920931837441497806860984
48403098129077791799088218795327364475675590848030
87086987551392711854517078544161852424320693150332
59959406895756536782107074926966537676326235447210
69793950679652694742597709739166693763042633987085
41052684708299085211399427365734116182760315001271
65378607361501080857009149939512557028198746004375
35829035317434717326932123578154982629742552737307
94953759765105305946966067683156574377167401875275
88902802571733229619176668713819931811048770190271
25267680276078003013678680992525463401061632866526
36270218540497705585629946580636237993140746255962
94
24074486908231174977792365466257246923322810917141
91430288197103288597806669760892938638285025333403
34413065578016127815921815005561868836468420090470
23053081172816430487623791969842487255036638784583
11487696932154902810424020138335124462181441773470
63783299490636259666498587618221225225512486764533
67720186971698544312419572409913959008952310058822
95548255300263520781532296796249481641953868218774
76085327132285723110424803456124867697064507995236
37774242535411291684276865538926205024910326572967
23701913275725675285653248258265463092207058596522
29798860272258331913126375147341994889534765745501
18495701454879288984856827726077713721403798879715
38298203783031473527721580348144513491373226651381
34829543829199918180278916522431027392251122869539
40957953066405232632538044100059654939159879593635
29746152185502371307642255121183693803580388584903
41698116222072977186158236678424689157993532961922
62467957194401269043877107275048102390895523597457
23189706772547915061505504953922979530901129967519
86188088225875314529584099251203829009407770775672
11306739708304724483816533873502340845647058077308
82959174767140363198008187129011875491310547126581
97623331044818386269515456334926366572897563400500
42846280183517070527831839425882145521227251250327
55121603546981200581762165212827652751691296897789
32238195734329339946437501907836945765883352399886
75506164965184775180738168837861091527357929701337
62177842752192623401942399639168044983993173312731
32924185707147349566916674687634660915035914677504
99518671430235219628894890102423325116913619626622
73267460800591547471830798392868535206946944540724
76841822524674417161514036427982273348055556214818
97142617910342598647204516893989422179826088076852
87783646182799346313767754307809363333018982642090
10848802521674670883215120185883543223812876952786
71329612474782464538636993009049310363619763878039
62184073572399794223406235393808339651327408011116
95
66627891981488087797941876876144230030984490851411
60661826293682836764744779239180335110989069790714
85786944089552990653640447425576083659976645795096
66024396409905389607120198219976047599490197230297
64913982680032973156037120041377903785566085089252
16730939319872750275468906903707539413042652315011
94809377245048795150954100921645863754710598436791
78639167021187492431995700641917969777599028300699
15368713711936614952811305876380278410754449733078
40789923115535562561142322423255033685442488917353
44889911501440648020369068063960672322193204149535
41503128880339536053299340368006977710650566631954
81234880673210146739058568557934581403627822703280
82616570773948327592232845941706525094512325230608
22918802058777319719839450180888072429661980811197
77158542502016545090413245809786882778948721859617
72107838435069186155435662884062257473692284509516
20849603980134001723930671666823555245252804609722
53503534226472524250874054075591789781264330331690
Большая сумма
Найдите первые десять цифр суммы ста 50-значных чисел.
Единственная проблема при решении этой задачи – найти тип данных, который мог бы хранить числа такой длины. К счастью, в паскале имеется
структура BigInteger для работы с целыми числами любой разрядности.
Исходные числа мы должны представить в программе в виде строк, которые затем конвертируем в большие числа методом Parse. Для удобства все
числа сохраним в массиве numbers:
uses MathExtensions;
var numbers := |
BigInteger.Parse('37107287533902102798797998220837590246510135740250'),
BigInteger.Parse('46376937677490009712648124896970078050417018260538'),
BigInteger.Parse('74324986199524741059474233309513058123726617309629'),
BigInteger.Parse('91942213363574161572522430563301811072406154908250'),
96
BigInteger.Parse('23067588207539346171171980310421047513778063246676'),
BigInteger.Parse('89261670696623633820136378418383684178734361726757'),
BigInteger.Parse('28112879812849979408065481931592621691275889832738'),
BigInteger.Parse('44274228917432520321923589422876796487670272189318'),
BigInteger.Parse('47451445736001306439091167216856844588711603153276'),
BigInteger.Parse('70386486105843025439939619828917593665686757934951'),
BigInteger.Parse('62176457141856560629502157223196586755079324193331'),
BigInteger.Parse('64906352462741904929101432445813822663347944758178'),
BigInteger.Parse('92575867718337217661963751590579239728245598838407'),
BigInteger.Parse('58203565325359399008402633568948830189458628227828'),
BigInteger.Parse('80181199384826282014278194139940567587151170094390'),
BigInteger.Parse('35398664372827112653829987240784473053190104293586'),
BigInteger.Parse('86515506006295864861532075273371959191420517255829'),
BigInteger.Parse('71693888707715466499115593487603532921714970056938'),
BigInteger.Parse('54370070576826684624621495650076471787294438377604'),
BigInteger.Parse('53282654108756828443191190634694037855217779295145'),
BigInteger.Parse('36123272525000296071075082563815656710885258350721'),
BigInteger.Parse('45876576172410976447339110607218265236877223636045'),
BigInteger.Parse('17423706905851860660448207621209813287860733969412'),
BigInteger.Parse('81142660418086830619328460811191061556940512689692'),
BigInteger.Parse('51934325451728388641918047049293215058642563049483'),
BigInteger.Parse('62467221648435076201727918039944693004732956340691'),
BigInteger.Parse('15732444386908125794514089057706229429197107928209'),
BigInteger.Parse('55037687525678773091862540744969844508330393682126'),
BigInteger.Parse('18336384825330154686196124348767681297534375946515'),
BigInteger.Parse('80386287592878490201521685554828717201219257766954'),
BigInteger.Parse('78182833757993103614740356856449095527097864797581'),
BigInteger.Parse('16726320100436897842553539920931837441497806860984'),
BigInteger.Parse('48403098129077791799088218795327364475675590848030'),
BigInteger.Parse('87086987551392711854517078544161852424320693150332'),
BigInteger.Parse('59959406895756536782107074926966537676326235447210'),
BigInteger.Parse('69793950679652694742597709739166693763042633987085'),
BigInteger.Parse('41052684708299085211399427365734116182760315001271'),
BigInteger.Parse('65378607361501080857009149939512557028198746004375'),
BigInteger.Parse('35829035317434717326932123578154982629742552737307'),
BigInteger.Parse('94953759765105305946966067683156574377167401875275'),
BigInteger.Parse('88902802571733229619176668713819931811048770190271'),
BigInteger.Parse('25267680276078003013678680992525463401061632866526'),
BigInteger.Parse('36270218540497705585629946580636237993140746255962'),
BigInteger.Parse('24074486908231174977792365466257246923322810917141'),
BigInteger.Parse('91430288197103288597806669760892938638285025333403'),
BigInteger.Parse('34413065578016127815921815005561868836468420090470'),
BigInteger.Parse('23053081172816430487623791969842487255036638784583'),
BigInteger.Parse('11487696932154902810424020138335124462181441773470'),
BigInteger.Parse('63783299490636259666498587618221225225512486764533'),
97
BigInteger.Parse('67720186971698544312419572409913959008952310058822'),
BigInteger.Parse('95548255300263520781532296796249481641953868218774'),
BigInteger.Parse('76085327132285723110424803456124867697064507995236'),
BigInteger.Parse('37774242535411291684276865538926205024910326572967'),
BigInteger.Parse('23701913275725675285653248258265463092207058596522'),
BigInteger.Parse('29798860272258331913126375147341994889534765745501'),
BigInteger.Parse('18495701454879288984856827726077713721403798879715'),
BigInteger.Parse('38298203783031473527721580348144513491373226651381'),
BigInteger.Parse('34829543829199918180278916522431027392251122869539'),
BigInteger.Parse('40957953066405232632538044100059654939159879593635'),
BigInteger.Parse('29746152185502371307642255121183693803580388584903'),
BigInteger.Parse('41698116222072977186158236678424689157993532961922'),
BigInteger.Parse('62467957194401269043877107275048102390895523597457'),
BigInteger.Parse('23189706772547915061505504953922979530901129967519'),
BigInteger.Parse('86188088225875314529584099251203829009407770775672'),
BigInteger.Parse('11306739708304724483816533873502340845647058077308'),
BigInteger.Parse('82959174767140363198008187129011875491310547126581'),
BigInteger.Parse('97623331044818386269515456334926366572897563400500'),
BigInteger.Parse('42846280183517070527831839425882145521227251250327'),
BigInteger.Parse('55121603546981200581762165212827652751691296897789'),
BigInteger.Parse('32238195734329339946437501907836945765883352399886'),
BigInteger.Parse('75506164965184775180738168837861091527357929701337'),
BigInteger.Parse('62177842752192623401942399639168044983993173312731'),
BigInteger.Parse('32924185707147349566916674687634660915035914677504'),
BigInteger.Parse('99518671430235219628894890102423325116913619626622'),
BigInteger.Parse('73267460800591547471830798392868535206946944540724'),
BigInteger.Parse('76841822524674417161514036427982273348055556214818'),
BigInteger.Parse('97142617910342598647204516893989422179826088076852'),
BigInteger.Parse('87783646182799346313767754307809363333018982642090'),
BigInteger.Parse('10848802521674670883215120185883543223812876952786'),
BigInteger.Parse('71329612474782464538636993009049310363619763878039'),
BigInteger.Parse('62184073572399794223406235393808339651327408011116'),
BigInteger.Parse('66627891981488087797941876876144230030984490851411'),
BigInteger.Parse('60661826293682836764744779239180335110989069790714'),
BigInteger.Parse('85786944089552990653640447425576083659976645795096'),
BigInteger.Parse('66024396409905389607120198219976047599490197230297'),
BigInteger.Parse('64913982680032973156037120041377903785566085089252'),
BigInteger.Parse('16730939319872750275468906903707539413042652315011'),
BigInteger.Parse('94809377245048795150954100921645863754710598436791'),
BigInteger.Parse('78639167021187492431995700641917969777599028300699'),
BigInteger.Parse('15368713711936614952811305876380278410754449733078'),
BigInteger.Parse('40789923115535562561142322423255033685442488917353'),
BigInteger.Parse('44889911501440648020369068063960672322193204149535'),
BigInteger.Parse('41503128880339536053299340368006977710650566631954'),
BigInteger.Parse('81234880673210146739058568557934581403627822703280'),
98
BigInteger.Parse('82616570773948327592232845941706525094512325230608'),
BigInteger.Parse('22918802058777319719839450180888072429661980811197'),
BigInteger.Parse('77158542502016545090413245809786882778948721859617'),
BigInteger.Parse('72107838435069186155435662884062257473692284509516'),
BigInteger.Parse('20849603980134001723930671666823555245252804609722'),
BigInteger.Parse('53503534226472524250874054075591789781264330331690')
|;
Собственно решение задачи занимает всего несколько строк в процедуре
Solve:
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// находим сумму всех чисел:
var sum := numbers.Sum;
Для выделения первых 10 цифр мы переводим число в строку, вырезаем из
неё 10 цифр, а затем снова конвертируем в число:
var res := Convert.ToInt64(sum.ToString().Substring(0, 10));
Writeln($' Первые 10 цифр суммы чисел: {res}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 13');
Writeln;
Solve();
end.
А вот и ответ подоспел:
Project Euler. Problem 13
99
Первые 10 цифр суммы чисел: 5537376230
Вы вполне можете избежать строковых операций, чтобы
решение было абсолютно «чистым».
Проект Эйлер014
Исходный код программы находится в файле Эйлер014.pas.
Задача 14 (Problem 14) Longest Collatz sequence была опубликована 5 апреля 2002 года.
Условие задачи на английском языке:
The following iterative sequence is defined for the set of positive integers:
n → n/2 (n is even)
n → 3n + 1 (n is odd)
Using the rule above and starting with 13, we generate the following sequence:
13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10
terms. Although it has not been proved yet (Collatz Problem), it is thought that all
starting numbers finish at 1.
Which starting number, under one million, produces the longest chain?
NOTE: Once the chain starts the terms are allowed to go above one million.
Самая длинная последовательность Коллатца
100
Следующая последовательность определена для натуральных чисел:
n → n/2 (n - чётное)
n → 3n + 1 (n - нечётное)
Используя описанное выше правило и начиная с 13, вы можете сгенерировать такую последовательность:
13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
Получившаяся последовательность 13 . . . 1 содержит 10 элементов. Хотя
это до сих пор и не доказано (проблема Коллатца (Collatz)), предполагается,
что все сгенерированные таким образом последовательности заканчиваются 1.
Какой начальный элемент меньше миллиона генерирует самую длинную последовательность?
Примечание: члены последовательности могут иметь значения больше
миллиона.
Самое главное в этой задаче – учесть замечание в конце её условия и правильно выбрать структуру данных для хранения членов последовательности. Опыт показывает, что вполне достаточно чисел типа int64.
Других препятствий в решении задачи методом грубой силы мы не встретим:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// число, начинающее последовательность:
var startNum: int64 := 0;
// макс. длина последовательности:
var maxLen := 0;
101
// макс. число в последовательности:
var maxNum: int64 := 0;
for var num := 1 to 1000000 do begin
var n: int64 := num;
var length := 1;
while (n <> 1) do begin
if (n mod 2 = 0) then n := n div 2
else n := 3 * n + 1;
if (n > maxNum) then maxNum := n;
length += 1;
end;
// запоминаем самую длинную последовательность:
if (length > maxLen) then begin
startNum := num;
maxLen := length;
end;
end;
Writeln($' Самая длинная последовательность начинается с:
{startNum}');
Writeln($' Её длина = {maxLen}');
Writeln;
Writeln($' Самое большое число в последовательностях = {maxNum}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 14');
Writeln;
Solve();
end.
Полный и развёрнутый ответ на задачу тут:
Project Euler. Problem 14
Самая длинная последовательность начинается с: 837799
Её длина = 525
102
Самое большое число в последовательностях = 56991483520
В сборнике головоломок Über 500 Brain Games: Denkspiele aus Wissenschaft,
Natur und Technik в задаче номер 533 Zahlenhagel мы снова встречаемся с
последовательностью Коллатца:
Denken Sie an eine Zahl. Wenn sie ungerade ist, nehmen Sie sie mal drei und addieren Sie 1; wenn sie gerade ist, teilen Sie sie durch 2. Mit jeder Zahl, die Sie in der
Folge erhalten, verfahren Sie ebenso.
Mit 1 beginnend, erhalten Sie: 1,4,2,1,4,2,1,4,2 etc.
Mit 2 beginnend, erhalten Sie: 2,1,4,2,1,4,2,1,4 etc.
Mit 3 beginnend, erhalten Sie: 3,10,5,16,8,4,2,1,4,2,1 etc.
Bald werden Sie merken, dass sie Sequenz in einer Endlosschlufe von 1-4-2-1-4-2
hängen bleibt. Ist das wirklich bei jeder Sequenz der Fall? Versuchen Sie mal mit der
7 zu beginnen.
Таким образом, наша задача состоит в том, чтобы вручную составить последовательность Коллатца, начинающуюся с 7.
Поскольку у нас есть компьютер, то пусть он и считает. А нам нужно написать перегрузку процедуры Solve - с параметром, который и обозначает
начало последовательности:
procedure Solve(start: int64);
begin
// число, начинающее последовательность:
var startNum := start;
// макс. число в последовательности:
var maxNum: int64 := 0;
var n := startNum;
// длина последовательности:
var length := 1;
while (n <> 1) do begin
Write(' ' + n + ' ');
if (n mod 2 = 0) then n := n div 2
else n := 3 * n + 1;
103
if (n > maxNum) then maxNum := n;
length += 1;
end;
Writeln;
Writeln($' Длина последовательности = {length}');
Writeln;
Writeln($' Самое большое число в последовательностях = {maxNum}');
Writeln;
end;
В книге приводится этот же ответ:
Project Euler. Problem 14
7 22 11 34 17 52 26 13
Длина последовательности = 17
40
20
10
5
16
8
4
2
Самое большое число в последовательностях = 52
Немцы называют эту задачу Hagelkörnerproblem, усматривая в бесконечном цикле чисел 1-4-2-1-4-2 Hagelkörner – градины - в грозовом облаке.
Также в ответе приводится информация об интересной последовательности, начинающейся с числа 27. Она довольно длинная, но при большом желании вы можете все выкладки арифметические провести вручную. Для
проверки вашей точности и бдительности привожу точное компьютерное
решение:
begin
Writeln(' Project Euler. Problem 14');
Writeln;
//Solve();
//Solve(7);
Solve(27);
end.
104
Project Euler. Problem 14
27 82 41 124 62 31 94 47 142 71 214 107 322 161 484 242
121 364 182 91 274 137 412 206 103 310 155 466 233 700 350
175 526 263 790 395 1186 593 1780 890 445 1336 668 334 167
502 251 754 377 1132 566 283 850 425 1276 638 319 958 479
1438 719 2158 1079 3238 1619 4858 2429 7288 3644 1822 911
2734 1367 4102 2051 6154 3077 9232 4616 2308 1154 577 1732
866 433 1300 650 325 976 488 244 122 61 184 92 46 23 70 35
106 53 160 80 40 20 10 5 16 8 4 2
Длина последовательности = 112
Самое большое число в последовательностях = 9232
Проект Эйлер015
Исходный код программы находится в файле Эйлер015.pas.
Задача 15 (Problem 15) Routes through a 20×20 grid была опубликована 19
апреля 2002 года.
Условие задачи на английском языке:
Starting in the top left corner of a 2×2 grid, there are 6 routes (without backtracking) to the bottom right corner.
How many routes are there through a 20×20 grid?
105
Пути в сетке 20 х 20
В сетке 2×2 можно проложить 6 маршрутов (двигаясь только вниз или
вправо), начинающихся в левом верхнем углу и заканчивающихся в правом
нижнем.
Сколько существует маршрутов в сетке 20×20?
Можно предположить, что число путей будет очень большим. Это значит,
что в обнимку с методом грубой силы мы будем бродить по сетке дольше,
чем Моисей по пустыне.
Впрочем, в задаче нетрудно разглядеть основания для применения метода
динамического программирования.
Как обычно, мы сначала рассмотрим минимальную сетку, состоящую из
одной клетки. Она имеет 2 вершины по горизонтали и столько же по вертикали.
В начале пути мы занимаем позицию в верхней левой вершине. Так как все
пути начинаются в этой вершине, то нет ни одного пути, ведущего к ней.
Поэтому мы ставим в эту вершину нулик. Число путей в остальные вершины нам пока неизвестно. Такие вершины мы помечаем крестиком (или
иксиком):
Двигаясь из начальной вершины строго вправо или вниз, мы окажемся в
правой верхней вершине или в левой нижней. Таким образом, существует
ровно 1 путь из начальной вершины в каждую из этих вершин:
106
Из этих вершин также существует по 1 пути в последнюю вершину. Итого
мы получаем 2 пути для одноклеточной сетки:
Расширим сетку до размеров 3 х 3 клетки и проставим число путей, которые
ведут в уже известные нам вершины:
107
Из вершин с единицей имеется единственный новый путь – вправо и вниз:
Легко видеть, что в любую вершину, лежащую на верхней или на левой стороне сетки, можно добраться единственным путём, иначе нам пришлось
бы идти вспять, что запрещено условиями задачи:
В вершины, отмеченные на рисунке⬆ красными крестиками, можно попасть из единичных вершин на сторонах сетки или из вершины с двойкой.
108
Но в вершины с 1 можно попасть единственным путём, а в вершину с 2 –
двумя. Итого в каждую из вершин с красным крестиком можно добраться
тремя разными путями:
Во вторую вершину на нижней стороне сетки можно попасть из угловой
вершины с единицей или из верхней с тройкой. Учитывая число путей в эти
вершины, мы получаем 4 пути для рассматриваемой вершины:
109
Рассуждая аналогично, мы должны прийти к выводу, что общее число путей
в заданную вершину равно сумме числа путей соседних с ней вершин –
слева и сверху:
Если положить нашу сетку на бочок, то легко заметить, что число путей в
ней образует треугольник Паскаля.
Правда, в начальной вершине сетки стоит не 1, а 0, но мы легко можем представить себе, что имеется и путь в начальную вершину из какой-нибудь
весьма удалённой точки.
В свою очередь, треугольник Паскаля состоит из биномиальных коэффициентов. А биномиальные коэффициенты равны числу подмножеств для
множеств с соответствующим числом элементов.
Вот теперь можно решать задачу!
110
В процедуре Solve мы должны учесть, что число вершин по обеим размерностям сетки на 1 больше числа клеток.
Чтобы избежать краевых эффектов, мы загодя проставляем 1 по левой и
верхней сторонам сетки – теперь все внутренние вершины имеют ровно
двух соседей сверху и слева:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// сетка:
var size := 20;
var grid := new int64[size + 1, size + 1];
// число путей из вершин,
// лежащих на верхней и левой сторонах сетки:
111
for var i := 1 to size do begin
grid[i, 0] := 1;
grid[0, i] := 1;
end;
for var row := 1 to size do
for var col := 1 to size do
grid[col, row] := grid[col - 1, row] + grid[col, row - 1];
После подсчёта путей для каждой вершины нам нужно прочитать число, получившееся в правой нижней вершине:
Writeln($' Число путей в сетке = {grid[size, size]}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 15');
Writeln;
Solve();
end.
Оно служит ответом на задачу:
Project Euler. Problem 15
Число путей в сетке = 137846528820
Проект Эйлер016
Исходный код программы находится в файле Эйлер016.pas.
112
Задача 16 (Problem 16) Power digit sum была опубликована 3 мая 2002 года.
Условие задачи на английском языке:
215 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26.
What is the sum of the digits of the number 21000?
Сумма цифр
215 = 32768, сумма цифр 3 + 2 + 7 + 6 + 8 = 26.
Чему равна сумма цифр числа 21000?
Так как число 21000 очень большое, то для его хранения потребуется переменная типа BigInteger, а для нахождения суммы цифр таких чисел мы пишем новую функцию для модуля MathExtensions:
// ВОЗВРАЩАЕТ СУММУ ЦИФР ЗАДАННОГО ЧИСЛА типа BigInteger
function Summa(num: BigInteger): int64;
begin
var sum := BigInteger(0);
while (num > 0) do begin
sum += num mod 10;
num := num div 10;
end;
Result := int64(sum);
end;
В процедуре Solve возводим двойку в тысячную степень и с помощью новой
функции находим сумму его цифр:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
113
begin
var pow := BigInteger.Pow(2, 1000);
Writeln($' 2 ^ 1000 = {pow}');
var sum := Summa(pow);
Writeln($' Сумма цифр = {sum}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 16');
Writeln;
Solve();
end.
В отличие от самого числа сумма его цифр совсем невелика!
Project Euler. Problem 16
2 ^ 1000 =
1071508607186267320948425049060001810561404811705533607443750388370351051
1249361224931983788156958581275946729175531468251871452856923140435984577
5746985748039345677748242309854210746050623711418779541821530464749835819
4126739876755916554394607706291457119647768654216766042983165262438683720
5668069376
Сумма цифр = 1366
Проект Эйлер017
Исходный код программы находится в файле Эйлер017.pas.
114
Задача 17 (Problem 17) Number letter counts была опубликована 17 мая
2002 года.
Условие задачи на английском языке:
If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there
are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.
If all the numbers from 1 to 1000 (one thousand) inclusive were written out in
words, how many letters would be used?
NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and fortytwo) contains 23 letters and 115 (one hundred and fifteen) contains 20 letters. The
use of "and" when writing out numbers is in compliance with British usage.
Число букв в числительных
Если записать числа от 1 до 5 английскими словами (one, two, three, four,
five), то используется 3 + 3 + 5 + 4 + 4 = 19 букв.
Сколько букв понадобится для записи всех чисел от 1 до 1000 (one
thousand) включительно?
Примечание: Не считайте пробелы и дефисы. Например, число 342 (three
hundred and forty-two) состоит из 23 букв, число 115 (one hundred and fifteen)
- из 20 букв. Использование "and" при записи чисел соответствует правилам
британского английского языка.
В процедуре Solve мы превращаем все числа в заданном диапазоне 1..1000
в слова, которые затем разбиваем на символы, и добавляем к результату
только буквы:
uses MathExtensions;
const
OneThousand = 1000;
OneMillion = 1000000;
OneBillion = 1000000000;
115
var words := new Dictionary<int64, string>;
procedure CreateDict();
begin
words.Add(0, 'zero');
words.Add(1, 'one');
words.Add(2, 'two');
words.Add(3, 'three');
words.Add(4, 'four');
words.Add(5, 'five');
words.Add(6, 'six');
words.Add(7, 'seven');
words.Add(8, 'eight');
words.Add(9, 'nine');
words.Add(10, 'ten');
words.Add(11, 'eleven');
words.Add(12, 'twelve');
words.Add(13, 'thirteen');
words.Add(14, 'fourteen');
words.Add(15, 'fifteen');
words.Add(16, 'sixteen');
words.Add(17, 'seventeen');
words.Add(18, 'eighteen');
words.Add(19, 'nineteen');
words.Add(20, 'twenty');
words.Add(30, 'thirty');
words.Add(40, 'forty');
words.Add(50, 'fifty');
words.Add(60, 'sixty');
words.Add(70, 'seventy');
words.Add(80, 'eighty');
words.Add(90, 'ninety');
words.Add(100, 'hundred');
words.Add(1000, 'thousand');
words.Add(1000000, 'million');
words.Add(1000000000, 'billion');
end;
function NumberToWords(number: int64): string;
begin
if number < 0 then
Result := 'negative ' + NumberToWords(-number)
else if number < 20 then
Result := words[number]
else if number < 100 then
116
begin
var tens := (number div 10) * 10;
var units := number mod 10;
Result := words[tens];
if units > 0 then
Result := Result + '-' + words[units];
end
else if number < 1000 then
begin
var hundreds := number div 100;
var remainder := number mod 100;
Result := words[hundreds] + ' hundred';
if remainder > 0 then
Result := Result + ' and ' + NumberToWords(remainder);
end
else if number < 1000000 then
begin
var thousands := number div 1000;
var remainder := number mod 1000;
Result := NumberToWords(thousands) + ' thousand';
if remainder > 0 then
begin
if remainder < 100 then
Result := Result + ' and ' + NumberToWords(remainder)
else
Result := Result + ', ' + NumberToWords(remainder);
end;
end
else if number < 1000000000 then
begin
var millions := number div 1000000;
var remainder := number mod 1000000;
Result := NumberToWords(millions) + ' million';
if remainder > 0 then
Result := Result + ', ' + NumberToWords(remainder);
end
else
begin
var billions := number div 1000000000;
var remainder := number mod 1000000000;
Result := NumberToWords(billions) + ' billion';
if remainder > 0 then
Result := Result + ', ' + NumberToWords(remainder);
end;
end;
117
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
CreateDict;
var totalLetters := 0;
for var i := 1 to 1000 do begin
var words := NumberToWords(i)
.Replace(' ', '')
.Replace('-', '')
.Replace(',', '');
totalLetters += words.Length;
end;
Writeln($' Всего букв в числах 1-1000: {totalLetters}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 17');
Writeln;
Solve();
end.
Задача решена быстро и аккуратно:
Project Euler. Problem 17
Всего букв в числах 1-1000: 21124
При желании или без оного вы можете урешать этого Эйлера по-русски:
uses MathExtensions;
const
Тысяча = 1000;
Миллион = 1000000;
Миллиард = 1000000000;
118
var слова := new Dictionary<int64, string>;
procedure CreateDict();
begin
слова.Add(0, 'ноль');
слова.Add(1, 'один');
слова.Add(2, 'два');
слова.Add(3, 'три');
слова.Add(4, 'четыре');
слова.Add(5, 'пять');
слова.Add(6, 'шесть');
слова.Add(7, 'семь');
слова.Add(8, 'восемь');
слова.Add(9, 'девять');
слова.Add(10, 'десять');
слова.Add(11, 'одиннадцать');
слова.Add(12, 'двенадцать');
слова.Add(13, 'тринадцать');
слова.Add(14, 'четырнадцать');
слова.Add(15, 'пятнадцать');
слова.Add(16, 'шестнадцать');
слова.Add(17, 'семнадцать');
слова.Add(18, 'восемнадцать');
слова.Add(19, 'девятнадцать');
слова.Add(20, 'двадцать');
слова.Add(30, 'тридцать');
слова.Add(40, 'сорок');
слова.Add(50, 'пятьдесят');
слова.Add(60, 'шестьдесят');
слова.Add(70, 'семьдесят');
слова.Add(80, 'восемьдесят');
слова.Add(90, 'девяносто');
слова.Add(100, 'сто');
слова.Add(200, 'двести');
слова.Add(300, 'триста');
слова.Add(400, 'четыреста');
слова.Add(500, 'пятьсот');
слова.Add(600, 'шестьсот');
слова.Add(700, 'семьсот');
слова.Add(800, 'восемьсот');
слова.Add(900, 'девятьсот');
слова.Add(1000, 'тысяча');
слова.Add(1000000, 'миллион');
слова.Add(1000000000, 'миллиард');
end;
119
function NumberToRussianWords(number: int64): string;
begin
if number < 0 then
Result := 'минус ' + NumberToRussianWords(-number)
else if number < 20 then
Result := слова[number]
else if number < 100 then
begin
var десятки := (number div 10) * 10;
var единицы := number mod 10;
Result := слова[десятки];
if единицы > 0 then
Result := Result + ' ' + слова[единицы];
end
else if number < 1000 then
begin
var сотни := (number div 100) * 100;
var остаток := number mod 100;
Result := слова[сотни];
if остаток > 0 then
Result := Result + ' ' + NumberToRussianWords(остаток);
end
else if number < 1000000 then
begin
var тысячи := number div 1000;
var остаток := number mod 1000;
// Склонение тысяч
var тысСлово := 'тысяч';
if (тысячи mod 10 = 1) and (тысячи mod 100 <> 11) then
тысСлово := 'тысяча'
else if (тысячи mod 10 in [2..4]) and not (тысячи mod 100 in
[12..14]) then
тысСлово := 'тысячи';
Result := NumberToRussianWords(тысячи) + ' ' + тысСлово;
if остаток > 0 then
Result := Result + ' ' + NumberToRussianWords(остаток);
end
else
Result := 'очень большое число';
end;
// РЕШАЕМ ЗАДАЧУ
120
procedure Solve();
begin
CreateDict;
var totalLetters := 0;
for var i := 1 to 1000 do begin
var words := NumberToRussianWords(i)
.Replace(' ', '')
.Replace('-', '')
.Replace(',', '');
totalLetters += words.Length;
end;
Writeln($' Всего букв в числах 1-1000: {totalLetters}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 17rus');
Writeln;
Solve();
end.
Весьма странно и загадочно, но русские слова оказались короче:
Project Euler. Problem 17rus
Всего букв в числах 1-1000: 18040
Проект Эйлер018
Исходный код программы находится в файле Эйлер018.pas.
121
Задача 18 (Problem 18) Maximum path sum I была опубликована 31 мая
2002 года.
Условие задачи на английском языке:
By starting at the top of the triangle below and moving to adjacent numbers on the
row below, the maximum total from top to bottom is 23.
3
74
246
8593
That is, 3 + 7 + 4 + 9 = 23.
Find the maximum total from top to bottom of the triangle below:
75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
122
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23
NOTE: As there are only 16384 routes, it is possible to solve this problem by trying
every route. However, Problem 67, is the same challenge with a triangle containing
one-hundred rows; it cannot be solved by brute force, and requires a clever method!
;o)
Максимальная сумма пути 1
Начиная в вершине треугольника (см. пример ниже) и перемещаясь вниз на
смежные числа до основания, мы получим максимальную сумму 3 + 7 + 4 + 9
= 23.
3
74
246
8593
Найдите максимальную сумму пути от вершины до основания следующего треугольника:
75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
123
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23
Примечание: Так как в данном треугольнике всего 16384 маршрута от вершины до основания, эту задачу можно решить, проверяя каждый из маршрутов. Однако похожая Задача 67 с треугольником, состоящим из сотни
строк, не решается перебором (brute force) и требует более умного подхода!
;o)
Числовой треугольник удобно представить в виде массива строк, в которых отдельные числа разделяет пробел:
uses MathExtensions;
// числовая пирамида:
var triangle :=
|'75',
'95 64',
'17 47 82',
'18 35 87 10',
'20 04 82 47 65',
'19 01 23 75 03 34',
'88 02 77 73 07 63 67',
'99 65 04 28 06 16 70 92',
'41 41 26 56 83 40 80 70 33',
'41 48 72 33 47 32 37 16 94 29',
'53 71 44 65 25 43 91 52 97 51 14',
'70 11 33 28 77 73 17 78 39 68 17 57',
'91 71 52 38 17 14 91 43 58 50 27 29 48',
'63 66 04 68 89 53 67 30 73 16 69 87 40 31',
'04 62 98 27 23 09 70 98 73 93 38 53 60 04 23'|;
В процедуре Solve мы последовательно получаем из этого массива строки,
разбиваем их на отдельные слова – строковые представления чисел, конвертируем слова в числа и помещаем их во временный список чисел очередной строки lst. И наконец, всю строку чисел в этом списке мы добавляем к
списку строк rows:
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
124
begin
// построчный список чисел:
var rows := new List<List<int>>();
foreach var s in triangle do begin
// разбиваем строку на отдельные "слова":
var wrd := s.Split(' ');
var lst := new List<int>();
foreach var w in wrd do begin
var num := int.Parse(w);
lst.Add(num);
end;
rows.Add(lst);
end;
Теперь все числа из треугольника получили прописку в списке rows, из которого извлекать отдельные числа просто и удобно.
Эта задача опять легко решается методом динамического программирования.
Пусть мы имеем минимальный треугольник:
2
85
Максимальная сумма пути в нём равна:
Max(8+2, 5+2) = 10
Увеличим высоту треугольника на 1 строку:
7
24
859
Мы уже знаем, что из двойки нужно пойти влево вниз, чтобы получить максимальную сумму 10. Аналогично для четвёрки: нужно пойти к девятке,
чтобы получить максимальную из возможных сумм:
Max(4+5, 4+9) = 13
Итак, двигаясь из семёрки, мы можем получить суммы:
125
10 13
Максимальная сумма всего пути равна:
Max(7+10, 7+13) = 20
Добавим ещё одну строку и получим треугольник из условия задачи:
3
74
246
8593
С учётом наших предварительных изысканий его можно переписать так:
3
74
10 13 15
Как мы выяснили, максимальная сумма пути из семёрки равна 20, а из четвёрки:
Max(4+13, 4+15) = 19
Тогда максимальная сумма пути из тройки:
3
20 19
равна:
Max(3+20, 3+19) = 23
Наглядно процесс прокладки максимального пути показан на рисунке↓.
Так как числа в основании треугольника являются конечными, то давайте
стартуем с последнего шага, который должен начаться с чисел 2, 4 или 6.
Если мы пойдём от двойки влево, то добавим к сумме пути 10, а если
вправо, то только 7. Вывод: от двойки нужно идти к восьмёрке.
126
Если путь оказался в четвёрке, то выгоднее идти вправо, добавляя к сумме
13.
Если путь оказался в шестёрке, то выгоднее идти влево, добавляя к сумме
15.
Поднимемся на один этаж выше.
Если путь оказался в семёрке, то выгоднее пойти вправо, добавляя к сумме
20. Если же путь оказался в четвёрке, то также выгоднее идти вправо, добавляя к сумме 19.
И вот мы оказались на вершине треугольника, с тройкой. Если мы пойдём
налево, то сумма чисел пути составит 23, а если направо, то 22. Ответ получен, и мы избежали утомительного перебора вариантов!
Итак, решая задачу, мы должны двигаться от основания треугольника к его
вершине. Для каждого числа в верхней строке мы находим максимальное из
двух чисел строкой ниже и заменяем верхнее число его суммой с этим числом.
В итоге мы найдём максимальную сумму пути из самого верхнего числа, которая и есть ответ на задачу:
// макс. сумма:
var maxs := 0;
// число строк в треугольнике:
127
var nRows := rows.Count();
// список текущих макс. значений сумм:
var maxValues := new int[nRows];
// макс. значения сумм в последней строке =
// числам в этой строке:
for var i := 0 to nRows-1 do
maxValues[i] := rows[nRows - 1][i];
for var r := nRows - 2 downto 0 do
for var c := 0 to r do begin
maxValues[c] := rows[r][c] +
Max(maxValues[c], maxValues[c + 1]);
end;
maxs := maxValues[0];
Writeln($' Максимальная сумма = {maxs}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 18');
Writeln;
Solve();
end.
Project Euler. Problem 18
Максимальная сумма = 1074
Проект Эйлер067
Исходный код программы находится в файле Эйлер067.pas.
128
Решение этой задачи методом динамического программирования ничем
не отличается от решения задачи №18.
Задача 67 (Problem 67) Maximum path sum II была опубликована 9 апреля
2004 года.
Условие задачи на английском языке:
By starting at the top of the triangle below and moving to adjacent numbers on the
row below, the maximum total from top to bottom is 23.
3
74
246
8593
That is, 3 + 7 + 4 + 9 = 23.
Find the maximum total from top to bottom in triangle.txt (right click and 'Save
Link/Target As...'), a 15K text file containing a triangle with one-hundred rows.
NOTE: This is a much more difficult version of Problem 18. It is not possible to try
every route to solve this problem, as there are 299 altogether! If you could check
one trillion (1012) routes every second it would take over twenty billion years to
check them all. There is an efficient algorithm to solve it. ;o)
Максимальная сумма пути 2
Начиная в вершине треугольника (см. пример ниже) и перемещаясь вниз на
смежные числа до основания, мы получим максимальную сумму 3 + 7 + 4 + 9
= 23.
3
74
246
8593
Найдите максимальную сумму при переходе от вершины до основания
треугольника, представленного текстовым файлом размером 15КБ
triangle.txt), в котором содержится треугольник с одной сотней строк.
129
ПРИМЕЧАНИЕ: Это намного усложненная версия 18-й задачи. Данную задачу
нельзя решить, испробовав каждый возможный вариант, поскольку всего их
299! Если бы удалось проверять один триллион (1012) вариантов в секунду,
то потребовалось бы двадцать биллионов лет чтобы испробовать их все.
Существует эффективный алгоритм решения данной задачи. ;o)
Так как теперь числовой треугольник хранится в текстовом файле, то мы
загружаем его в строковый массив triangle с диска, а весь остальной код метода Solve остаётся без изменений:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var path := 'p067_triangle.txt';
var triangle := ReadAllLines(path);
// построчный список чисел:
var rows := new List<List<int>>();
foreach var s in triangle do begin
// разбиваем строку на отдельные "слова":
var wrd := s.Split(' ');
var lst := new List<int>();
foreach var w in wrd do begin
var num := int.Parse(w);
lst.Add(num);
end;
rows.Add(lst);
end;
// макс. сумма:
var maxs := 0;
// число строк в треугольнике:
var nRows := rows.Count();
// список текущих макс. значений сумм:
var maxValues := new int[nRows];
// макс. значения сумм в последней строке =
// числам в этой строке:
for var i := 0 to nRows-1 do
130
maxValues[i] := rows[nRows - 1][i];
for var r := nRows - 2 downto 0 do
for var c := 0 to r do begin
maxValues[c] := rows[r][c] +
Max(maxValues[c], maxValues[c + 1]);
end;
maxs := maxValues[0];
Writeln($' Максимальная сумма = {maxs}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 67');
Writeln;
Solve();
end.
Вместо биллионов лет (по-русски говоря, миллиардов), которыми нас пугали составители задачи, компьютеру потребовались доли секунды, чтобы
решить задачу:
Project Euler. Problem 67
Максимальная сумма = 7273
Алгоритмы – это сила!
Проект Эйлер019
Исходный код программы находится в файле Эйлер019.pas.
131
Задача 19 (Problem 19) Counting Sundays была опубликована 14 июня 2004
года.
Условие задачи на английском языке:
You are given the following information, but you may prefer to do some research for
yourself.
• 1 Jan 1900 was a Monday.
• Thirty days has September,
April, June and November.
All the rest have thirty-one,
Saving February alone,
Which has twenty-eight, rain or shine.
And on leap years, twenty-nine.
• A leap year occurs on any year evenly divisible by 4, but not on a century unless it
is divisible by 400.
How many Sundays fell on the first of the month during the twentieth century (1 Jan
1901 to 31 Dec 2000)?
Считаем воскресенья
Дана следующая информация (однако, вы можете проверить её самостоятельно):
•
•
•
1 января 1900 года - понедельник.
В сентябре, апреле, июне и ноябре 30 дней.
В феврале 28, в високосный год - 29.
В остальных месяцах по 31 дню.
Високосный год - любой год, делящийся нацело на 4, однако первый год
века является високосным только том случае, если делится на 400.
Сколько воскресений выпадает на первое число месяца в двадцатом
веке (с 1 января 1901 года по 31 декабря 2000 года)?
Самая сложная закавыка в этой задаче – определить день недели по дате –
легко решается с помощью свойства DayOfWeek структуры DateTime:
132
uses MathExtensions, System;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var count := 0;
for var year := 1901 to 2000 do
for var month := 1 to 12 do begin
// создаём дату - первое число месяца:
var dt := new DateTime(year, month, 1);
// проверяем на воскресенье:
if dt.DayOfWeek = DayOfWeek.Sunday then
count += 1;
end;
Writeln($' Число воскресений = {count}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 19');
Writeln;
Solve();
end.
Итак, в 20 веке 171 раз месяц начинался с воскресенья:
Project Euler. Problem 19
Число воскресений = 171
Вот решение задачи для любителей однострочников, подстрочников и
криптографии:
procedure Solve2();
begin
var count := Range(1901, 2000)
.SelectMany(year -> Range(1, 12)
133
.Select(month -> new DateTime(year, month, 1)))
.Count(dt -> dt.DayOfWeek = DayOfWeek.Sunday);
Writeln($' Число воскресений = {count}');
Writeln;
end;
Решение задачи для любителей функционального программирования:
function GetSundayFirsts(startYear, endYear: integer): sequence of
DateTime;
begin
for var year := startYear to endYear do
for var month := 1 to 12 do begin
var dt := new DateTime(year, month, 1);
if dt.DayOfWeek = DayOfWeek.Sunday then
yield dt;
end;
end;
procedure Solve3();
begin
var sundays := GetSundayFirsts(1901, 2000).ToArray;
Writeln($' Число воскресений = {sundays.Length}');
Writeln;
end;
Для интереса любознательности вы можете распечатать все воскресенья
20-го века, с которых начинается месяц:
procedure Solve3();
begin
var sundays := GetSundayFirsts(1901, 2000).ToArray;
Writeln($' Число воскресений = {sundays.Length}');
Writeln;
for var i := 0 to sundays.Length - 1 do
Writeln(' ', sundays[i].ToString('dd.MM.yyyy'));
end;
134
01.09.1901
01.12.1901
01.06.1902
01.02.1903
01.03.1903
01.11.1903
01.05.1904
01.01.1905
. . .
01.02.1998
01.03.1998
01.11.1998
01.08.1999
01.10.2000
Поскольку мы использовали специфические возможности паскаля, то давайте решим задачу по-честному:
function IsSunday(year, month, day: int): bool;
begin
// Алгоритм Зеллера для определения дня недели
// Возвращает 0=суббота, 1=воскресенье, ..., 6=пятница
if month < 3 then begin
month := month + 12;
year := year - 1;
end;
var K := year mod 100;
var J := year div 100;
var h := (day + ((13 * (month + 1)) div 5) +
K + (K div 4) + (J div 4) +
5 * J) mod 7;
// h = 1 - воскресенье:
Result := h = 1;
end;
procedure Solve4;
begin
var count := 0;
135
for var year := 1901 to 2000 do
for var month := 1 to 12 do
if IsSunday(year, month, 1) then
count += 1;
Writeln($' Число воскресений = {count}');
Writeln;
end;
Проект Эйлер020
Исходный код программы находится в файле Эйлер020.pas.
Задача 20 (Problem 20) Factorial digit sum была опубликована 21 июня
2004 года.
Условие задачи на английском языке:
n! means n × (n − 1) × ... × 3 × 2 × 1
For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800,
and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27.
Find the sum of the digits in the number 100!
Сумма цифр факториала
n! означает n × (n − 1) × ... × 3 × 2 × 1
Найдите сумму цифр в числе 100!
Добавляем в модуль BigIntegerExtensions новый метод расширения:
/// ВОЗВРАЩАЕТ ФАКТОРИАЛ ЗАДАННОГО ЧИСЛА
136
function FactorialBig(self: Integer): BigInteger; extensionmethod;
begin
if self < 0 then
raise new System.ArgumentOutOfRangeException('Factorial is not defined for negative numbers');
if self = 0 then exit(1);
Result := 1;
var i := BigInteger(1);
while i <= self do begin
Result := Result * i;
i := i + 1;
end;
end;
И решаем задачу в одно действие:
uses BigIntegerExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var sum := 100.FactorialBig.DigitSumBig;
Writeln($' Сумма цифр факториала = {sum}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 20');
Writeln;
Solve();
end.
Сумму цифр длинных чисел мы уже научились подсчитывать, так что задача решена!
137
Project Euler. Problem 20
Сумма цифр факториала = 648
Проект Эйлер021
Исходный код программы находится в файле Эйлер021.pas.
Задача 21 (Problem 21) называется Amicable Numbers.
Дружественные числа
Пусть d(n) определяется как сумма делителей n (числа меньше n, делящие n
нацело).
Если d(a) = b и d(b) = a, где a ≠ b, то a и b называются дружественной парой,
а каждое из чисел a и b - дружественным числом.
Например, делителями числа 220 являются 1, 2, 4, 5, 10, 11, 20, 22, 44, 55 и 110,
поэтому d(220) = 284. Делители 284 - 1, 2, 4, 71, 142, поэтому d(284) = 220.
Подсчитайте сумму всех дружественных чисел меньше 10000.
Два (различных) натуральных числа называются дружественными, если
сумма всех делителей (исключая само число) первого числа равна второму
138
числу, и наоборот. Первая пара дружественных чисел была найдена несколько тысячелетий тому назад. Это числа 220 и 284. Следующая пара
отыскалась только в 1866 году – 1184 и 1210 (но раньше уже были известны другие дружественные числа). Сейчас список дружественных чисел
включает миллиарды пар. Оба числа в паре либо оба чётные, либо оба нечётные.
Как обычно и принято, пусть задачу решает процедура Solve:
begin
Writeln(' Project Euler. Problem 21');
Writeln;
Solve();
end.
Но ей не обойтись без функции SumOfDivisors, которая возвращает сумму
делителей заданного числа:
uses MathExtensions;
function SumOfDivisors(n: int): int;
begin
if n < 2 then exit(0);
// 1 всегда делитель:
var sum := 1;
var limit := ISqrt(n);
for var i := 2 to limit do begin
if n mod i = 0 then begin
sum += i;
var complement := n div i;
if complement <> i then
sum := sum + complement;
end;
end;
Result := sum;
end;
139
А теперь во всю прыть решаем задачу:
const limit = 10000;
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
var totalSum := 0;
Для простоты и уменьшения перебора запишем суммы всех чисел для указанного диапазона в массив divisorSums:
// вычисляем суммы делителей для всех чисел:
var divisorSums : array[0..limit + 1] of int;
for var a := 2 to limit do
divisorSums[a] := SumOfDivisors(a);
Все суммы загнаны в массив. Теперь мы можем подыскать большого
дружка для каждого меньшего числа. Вынимаем из массива сумму делителей
числа а и проверяем, есть ли в массиве сумма делителей для числа b, которая равна числу а. Если таковая найдётся, то оба числа подружатся:
// дружественные пары:
Writeln($' Дружественные числа до {limit}:');
Writeln(' -----------------------------');
for var a := 2 to limit do begin
var b := divisorSums[a];
if (b > a) and (b <= limit) then begin
if divisorSums[b] = a then begin
Writeln($' {a} и {b}');
totalSum += a + b;
end;
end;
end;
Writeln(' -----------------------------');
Writeln($' Сумма: {totalSum}');
end;
140
Дружеская задача решена по-дружески:
Project Euler. Problem 21
Дружественные числа до 10000:
----------------------------220 и 284
1184 и 1210
2620 и 2924
5020 и 5564
6232 и 6368
----------------------------Сумма: 31626
Увеличив десятырежды значение константы limit,
const limit = 100000;
мы найдём и более крутые пары:
Project Euler. Problem 21
Дружественные числа до 100000:
----------------------------220 и 284
1184 и 1210
2620 и 2924
5020 и 5564
6232 и 6368
10744 и 10856
12285 и 14595
17296 и 18416
63020 и 76084
66928 и 66992
67095 и 71145
69615 и 87633
79750 и 88730
----------------------------141
Сумма: 852810
Ещё раз удесятеряем константу:
const limit = 1000000;
Project Euler. Problem 21
Дружественные числа до 1000000:
----------------------------220 и 284
1184 и 1210
2620 и 2924
5020 и 5564
6232 и 6368
10744 и 10856
12285 и 14595
17296 и 18416
63020 и 76084
66928 и 66992
67095 и 71145
69615 и 87633
79750 и 88730
100485 и 124155
122265 и 139815
122368 и 123152
141664 и 153176
142310 и 168730
171856 и 176336
176272 и 180848
185368 и 203432
196724 и 202444
280540 и 365084
308620 и 389924
319550 и 430402
356408 и 399592
437456 и 455344
469028 и 486178
503056 и 514736
522405 и 525915
600392 и 669688
142
609928 и 686072
624184 и 691256
635624 и 712216
643336 и 652664
667964 и 783556
726104 и 796696
802725 и 863835
879712 и 901424
898216 и 980984
----------------------------Сумма: 25275024
И для пущего удовлетворения любознательности последний раз удесятеряем эту константу:
const limit = 10000000;
Вот как крепко мы подружились с дружественными числами!
Project Euler. Problem 21
Дружественные числа до 10000000:
----------------------------220 и 284
1184
2620
5020
6232
10744
12285
17296
63020
66928
67095
69615
79750
и
и
и
и
и
и
и
и
и
и
и
и
1210
2924
5564
6368
10856
14595
18416
76084
66992
71145
87633
88730
635624
643336
667964
726104
802725
879712
898216
и
и
и
и
и
и
и
712216
652664
783556
796696
863835
901424
980984
947835 и 1125765
998104 и 1043096
1077890
1154450
1156870
1175265
и
и
и
и
1099390
1189150
1292570
1438983
3805264
4238984
4246130
4259750
4482765
4532710
4604776
5123090
5147032
5232010
5357625
5385310
5459176
5726072
5730615
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
4006736
4314616
4488910
4445050
5120595
6135962
5162744
5504110
5843048
5799542
5684679
5812130
5495264
6369928
6088905
143
100485
122265
122368
141664
142310
171856
176272
185368
196724
280540
308620
319550
356408
437456
469028
503056
522405
600392
609928
624184
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
124155
139815
123152
153176
168730
176336
180848
203432
202444
365084
389924
430402
399592
455344
486178
514736
525915
669688
686072
691256
1185376
1280565
1328470
1358595
1392368
1466150
1468324
1511930
1669910
1798875
2082464
2236570
2652728
2723792
2728726
2739704
2802416
2803580
3276856
3606850
3786904
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
и
1286744
1340235
1483850
1486845
1464592
1747930
1749212
1598470
2062570
1870245
2090656
2429030
2941672
2874064
3077354
2928136
2947216
3716164
3721544
3892670
4300136
5864660 и 7489324
6329416 и 6371384
6377175 и 6680025
6955216 и 7418864
6993610 и 7158710
7275532 и 7471508
7288930 и 8221598
7489112 и 7674088
7577350 и 8493050
7677248 и 7684672
7800544 и 7916696
7850512 и 8052488
8262136 и 8369864
8619765 и 9627915
9071685 и 9498555
9199496 и 9592504
9339704 и 9892936
9363584 и 9437056
----------------Сумма: 575875320
Для углубления и усугубления знаний о дружественных числах вдогонку
пишем ещё одну, коротюсенькую программульку:
##
// Подробный разбор примера 220 и 284
Writeln(' Пример: пара 220 и 284');
Writeln('=====================');
// Делители числа 220:
Writeln(' Делители 220:');
var divisors220 := new List<integer>;
for var i := 1 to 219 do
if 220 mod i = 0 then
divisors220.Add(i);
Writeln(' ' + string.Join(' + ', divisors220));
var sum220 := divisors220.Sum;
Writeln($' Сумма делителей 220 = {sum220}');
144
Writeln;
// Делители числа 284:
Writeln(' Делители 284:');
var divisors284 := new List<integer>;
for var i := 1 to 283 do
if 284 mod i = 0 then
divisors284.Add(i);
Writeln(' ' + string.Join(' + ', divisors284));
var sum284 := divisors284.Sum;
Writeln($' Сумма делителей 284 = {sum284}');
Writeln;
// проверяем на дружественность:
if (sum220 = 284) and (sum284 = 220) then begin
Writeln(' ✓ 220 и 284 - дружественные числа!');
Writeln(' d(220) = 284');
Writeln(' d(284) = 220');
end
else
Writeln('✗ 220 и 284 - НЕ дружественные');
И всё-таки они дружественные!
Пример: пара 220 и 284
=====================
Делители 220:
1 + 2 + 4 + 5 + 10 + 11 + 20 + 22 + 44 + 55 + 110
Сумма делителей 220 = 284
Делители 284:
1 + 2 + 4 + 71 + 142
Сумма делителей 284 = 220
✓ 220 и 284 - дружественные числа!
d(220) = 284
d(284) = 220
145
Проект Эйлер022
Исходный код программы находится в файле Эйлер022.pas.
Задача 22 (Problem 22) называется Names Scores.
Очки за имена
Используйте names.txt (щёлкнуть правой кнопкой мыши и выбрать 'Save
Link/Target As...'), текстовый файл размером 46 КБ, содержащий более пяти
тысяч имен. Начните с сортировки в алфавитном порядке. Затем подсчитайте алфавитные значения каждого имени и умножьте это значение на
порядковый номер имени в отсортированном списке для получения количества очков имени.
Например, если список отсортирован по алфавиту, имя COLIN (алфавитное
значение которого 3 + 15 + 12 + 9 + 14 = 53) является 938-м в списке. Поэтому,
имя COLIN получает 938 × 53 = 49714 очков.
Какова сумма очков имён в файле?
Начало текстового файла выглядит так:
"MARY","PATRICIA","LINDA","BARBARA","ELIZABETH","JENNIFER", …
Запятые и кавычки нужны здесь исключительно для усложнения задачи.
Из-за этого и потомучто мы вынуждены принуждением написать функцию
146
ParseNames, которая принимает длинную строку со всеми именами, кавычками и запятыми, а возвращает аккуратный список имён:
function ParseNames(text: string): List<string>;
begin
Result := new List<string>;
var name := '';
var inQuotes := false;
for var i := 1 to text.Length do begin
var ch := text[i];
case ch of
'"': begin
if inQuotes then begin
// закрывающая кавычка:
if name.Length > 0 then begin
Result.Add(name);
name := '';
end;
end;
inQuotes := not inQuotes;
end;
'A'..'Z':
if inQuotes then
name += ch;
// остальные символы пропускаем
end;
end;
end;
Для решения задачи нам также нужна функция AlphabeticalValue, которая
получает от нас имя, а возвращает очки за него:
uses MathExtensions;
function AlphabeticalValue(name: string): int;
begin
Result := 0;
foreach var ch in name do
Result += Ord(ch) - Ord('A') + 1;
147
end;
Если бы не эти тяготы, задача решалась бы гораздо проще – поимённым голосованием:
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var path := 'p022_names.txt';
var text := ReadAllLines(path)[0];
var names := ParseNames(text);
Writeln('Найдено имен: ', names.Count);
// сортируем:
names.Sort;
// для проверки печатаем первые 5 имен:
Writeln('Первые 5 имен после сортировки:');
for var i := 0 to Min(4, names.Count - 1) do
Writeln($' {i + 1}: {names[i]}');
// вычисляем результат:
var totalScore: int64 := 0;
for var i := 0 to names.Count - 1 do
totalScore += AlphabeticalValue(names[i]) * (i + 1);
Writeln;
Writeln($' Суммарный результат: {totalScore}');
// правильный ответ: 871198282
Writeln;
end;
begin
Writeln(' Project Euler. Problem 22');
Writeln;
Solve();
end.
148
И что же мы видим на экране после запуска программы? – Она работает исправно и покладисто, выдав правильный ответ на очередную эйлерову закавыку:
Project Euler. Problem 22
Найдено имен: 5163
Первые 5 имен после сортировки:
1: AARON
2: ABBEY
3: ABBIE
4: ABBY
5: ABDUL
Суммарный результат: 871198282
Проект Эйлер023
Исходный код программы находится в файле Эйлер023.pas.
Задача 23 (Problem 23) называется Non-Abundant Sums.
Недостаточные суммы
149
Совершенным числом называется число, у которого сумма его делителей
равна самому числу. Например, сумма делителей числа 28 равна 1 + 2 + 4 + 7
+ 14 = 28, что означает, что число 28 является совершенным числом.
Число n называется недостаточным, если сумма его делителей меньше n, и
называется избыточным, если сумма его делителей больше n.
Так как число 12 является наименьшим избыточным числом (1 + 2 + 3 + 4 + 6
= 16), наименьшее число, которое может быть записано как сумма двух избыточных чисел, равно 24. Используя математический анализ, можно показать, что все целые числа больше 28123 могут быть записаны как сумма
двух избыточных чисел. Эта граница не может быть уменьшена дальнейшим анализом, даже несмотря на то, что наибольшее число, которое не может быть записано как сумма двух избыточных чисел, меньше этой границы.
Найдите сумму всех положительных чисел, которые не могут быть
записаны как сумма двух избыточных чисел.
Сумма собственных делителей числа называется аликвотной суммой.
Если аликвотная сумма равна числу, то число совершенное.
Если аликвотная сумма больше числа, то число избыточное.
Если аликвотная сумма меньше числа, то число недостаточное.
Нам нужно проверить все натуральные числа ≤ 28123.
При внимательном чтении условия задачи становится ясно, что нам нужны
все делители заданного числа, а также их сумма. Для этого в модуле
MathExtensions у нас есть метод расширения Divisors, который возвращает все делители заданного числа, включая само число, от которого мы
избавляемся. Метод расширения Sum находит сумму всех делителей:
// ВОЗВРАЩАЕТ ВСЕ ДЕЛИТЕЛИ ЧИСЛА,
// ВКЛЮЧАЯ САМО ЧИСЛО
function Divisors(self: int): sequence of int; extensionmethod;
begin
var n := Abs(self);
// возвращаем 1:
yield 1;
150
if n = 1 then exit;
// возвращаем делители 2.. < n:
for var i := 2 to ISqrt(n) do begin
if n mod i = 0 then begin
yield i;
var complement := n div i;
if complement <> i then
yield complement;
end;
end;
// возвращаем само число:
yield n;
end;
uses MathExtensions;
// ВОЗВРАЩАЕТ СУММУ ВСЕХ ДЕЛИТЕЛЕЙ ЧИСЛА,
// КРОМЕ САМОГО ЧИСЛА
function SumOfDivisors(n: int) := n.Divisors.Sum - n;
Зная сумму делителей, мы легко, но быстро просеем избыточные числа:
// ИЗБЫТОЧНОЕ ЧИСЛО?
function IsAbundant(n: int) := SumOfDivisors(n) > n;
Можно браться за задачу:
begin
Writeln(' Project Euler. Problem 23');
Writeln;
Solve();
end.
А берёмся мы за неё в процедуре по имени Solve:
151
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var limit := 28123;
Собираем все избыточные числа в список:
// все избыточные числа до limit:
var abundantNumbers := new List<int>;
Заполняем список избыточными числами, начиная с дюжинного числа 12:
// 12 - первое избыточное число:
for var i := 12 to limit do
if IsAbundant(i) then
abundantNumbers.Add(i);
Writeln($' Найдено избыточных чисел: {abundantNumbers.Count}');
Writeln(' Первые 10 избыточных чисел:');
for var i := 0 to Min(9, abundantNumbers.Count - 1) do
Write($' {abundantNumbers[i]} ');
Writeln;
Теперь мы отыщем числа, которые можно представить как сумму двух избыточных чисел, из готового списка таких чисел: abundantNumbers:
// отмечаем все числа,
// которые можно представить как сумму двух избыточных:
var canBeExpressed := new bool[limit + 1];
for var i := 0 to abundantNumbers.Count - 1 do
for var j := i to abundantNumbers.Count - 1 do begin
Перебираем все пары избыточных чисел, которые могут дать число, не превышающее limit:
152
var sum := abundantNumbers[i] + abundantNumbers[j];
Отмечаем «трюизмами» числа, которые не подпадают под условие задачи:
if sum <= limit then
canBeExpressed[sum] := true;
end;
Находим сумму остальных чисел. Они имеют ложное значение в логическом
массиве canBeExpressed:
// суммируем числа,
// которые нельзя выразить как сумму двух избыточных:
var totalSum := 0;
var count := 0;
for var i := 1 to limit do
if not canBeExpressed[i] then begin
totalSum += i;
count += 1;
end;
Отчитываемся перед герром Эйлером о проделанной работе:
Writeln;
Writeln($' Всего таких чисел: {count}');
Writeln($' Их сумма = {totalSum}');
Writeln;
// ответ: 4179871
end;
Отчёт принят:
Project Euler. Problem 23
153
Найдено избыточных чисел: 6965
Первые 10 избыточных чисел:
12 18 20 24 30 36 40 42
48
54
Всего таких чисел: 1456
Их сумма = 4179871
Проект Эйлер024
Исходный код программы находится в файле Эйлер024.pas.
Задача 24 (Problem 24) называется Lexicographic Permutations.
A permutation is an ordered arrangement of objects. For example, 3124 is one possible permutation of the digits 1, 2, 3 and 4. If all of the permutations are listed numerically or alphabetically, we call it lexicographic order. The lexicographic permutations of 0, 1 and 2 are:
012 021 102 120 201 210
What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8
and 9?
Лексикографические перестановки
Перестановка - это упорядоченная выборка объектов. К примеру, 3124 является одной из возможных перестановок из цифр 1, 2, 3 и 4. Если все перестановки приведены в порядке возрастания или в алфавитном порядке, то такой порядок будем называть лексикографическим. Лексикографические перестановки из цифр 0, 1 и 2 представлены ниже:
012 021 102 120 201 210
154
Какова миллионная лексикографическая перестановка из цифр 0, 1, 2,
3, 4, 5, 6, 7, 8 и 9?
В паскале есть метод расширения Permutations для массивов и последовательностей, поэтому мы можем поместить все цифры в один из этих типов
данных:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// цифры:
var digits := (0..9);
// номер нужной перестановки
var target := 1000000;
var count := 0;
var res := '';
И в цикле foreach спокойно и аккуратно считаем перестановки этих цифр:
// перебираем все перестановки
foreach var perm in digits.Permutations do begin
count += 1;
if count = target then begin
Метод расширения Permutations возвращает массив цифр, поэтому мы
формируем из них строку:
// собираем перестановку в строку:
foreach var digit in perm do
res += digit.ToString;
break;
end;
end;
155
И печатаем на экране:
Writeln($' Миллионная перестановка: {result}');
Writeln;
// ответ: 2783915460
end;
begin
Writeln(' Project Euler. Problem 24');
Writeln;
Solve();
end.
Паскаль нас не подвёл:
Project Euler. Problem 24
Миллионная перестановка: 2783915460
156
Проект Эйлер025
Исходный код программы находится в файле Эйлер025.pas.
Задача 25 (Problem 25) называется 1000-digit Fibonacci Number.
1000-значное число Фибоначчи
Последовательность Фибоначчи определяется рекурсивным правилом:
Fn = Fn−1 + Fn−2, где F1 = 1 и F2 = 1.
Таким образом, первые 12 членов последовательности равны:
F1 = 1
F2 = 1
F3 = 2
F4 = 3
F5 = 5
157
F6 = 8
F7 = 13
F8 = 21
F9 = 34
F10 = 55
F11 = 89
F12 = 144
Двенадцатый член F12 - первый член последовательности, который содержит
три цифры.
Каков порядковый номер первого члена последовательности Фибоначчи,
содержащего 1000 цифр?
Числа Фибоначчи известны даже зайцам, поэтому мы сразу вытаскиваем из
шляпы этих зайцев:
begin
Writeln(' Project Euler. Problem 25');
WriteLn;
Solve();
end.
Сначала мы обойдёмся своими силами и самостоятельно вычислим все
числа Фибоначчи. Небольшие неприятности доставят нам большие числа –
и только:
procedure Solve();
begin
var a := BigInteger.One;
var b := BigInteger.One;
var id := 2;
// F(1)
// F(2)
// Индекс текущего числа b
while true do begin
// печатаем ответ:
if b.ToString.Length >= 1000 then begin
Writeln($' Порядковый номер = {id}');
158
break;
end;
// вычисляем следующее число Фибоначчи:
(a, b) := (b, (a + b));
id += 1;
end;
// 4782
end;
И раз такое дело и на то дело пошло, давайте напишем новый модуль
FibonacciBigInt, а в нём – генератор зайцев SequenceFibo:
159
/// ГЕНЕРИРУЕТ БОЛЬШИЕ ЧИСЛА ФИБОНАЧЧИ
class function Fibonacci.SequenceFibo(): sequence of BigInteger;
begin
var a := BigInteger.Zero;
var b := BigInteger.One;
yield a; // F₀
yield b; // F₁
while true do
begin
var next := a + b;
yield next;
a := b;
b := next;
end;
end;
Теперь мы рассчитаемся с зайцами и того проще:
procedure Solve2();
begin
var id := 0;
foreach var f in Fibonacci.SequenceFibo() do begin
// печатаем ответ:
if f.ToString.Length >= 1000 then begin
Writeln($' Порядковый номер = {id}');
break;
end;
id += 1;
end;
WriteLn;
end;
И вот их сколько наплодилось:
Project Euler. Problem 25
Порядковый номер = 4782
Порядковый номер = 4782
160
Проект Эйлер026
Исходный код программы находится в файле Эйлер026.pas.
Задача 26 (Problem 26) называется Reciprocal Cycles.
Длинные периоды
Единичная дробь имеет 1 в числителе. Десятичные представления единичных
дробей со знаменателями от 2 до 10 даны в таблице.
1
0.1(6) значит 0.166666..., и имеет повторяющуюся последовательность из одной цифры. Заметим, что 1/7 имеет повторяющуюся
последовательность из 6 цифр.
Найдите значение d < 1000, для которого 1/d в десятичном виде
содержит самую длинную повторяющуюся последовательность
цифр.
/2 = 0.5
/3 = 0.(3)
1
/4 = 0.25
1
/5 = 0.2
1
/6 = 0.1(6)
1
/7 = 0.(142857)
1
/8 = 0.125
1
/9 = 0.(1)
1
/10 = 0.1
1
О циклических числах читайте дальше. Из этого проекта мы и возьмём код
для решения этой задачи.
161
Поскольку мы в будущем мы уже решили проблему с вычислением периодов простых дробей, то с помощью машины времени мы скопипастим код
оттуда и добавим ему уму в соответствии с условиями этой задачи.
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
Эти переменные нам нужны для запоминания информации о самом длинном периоде и о числе, который его порождает:
var long := 0;
var longd := 0;
var longp := String.Empty;
В данном случае мы проверяем все числа в заданном диапазоне, а не только
простые числа, что было бы правильно:
for var d := 2 to 1000 do begin
// алгоритм деления в столбик для нахождения периода:
var remainder := 1;
var digits := new List<char>;
var remainders := new Dictionary<int, int>;
var position := 0;
var periodFound := false;
var periodStart := 0;
while not periodFound and (position < 2000) do begin
if remainders.ContainsKey(remainder) then begin
periodFound := true;
periodStart := remainders[remainder];
end
else begin
remainders[remainder] := position;
remainder := remainder * 10;
digits.Add(char(ord('0') + remainder div d));
162
remainder := remainder mod d;
position += 1;
end;
end;
Период для очередного числа найден. Если он длиннее рекордного, то запоминаем его в вышеопределённых переменных:
if periodFound then begin
var periodLength := digits.Count - periodStart;
if periodLength > long then begin
long := periodLength;
longd := d;
var p := String.Empty;
for var i := periodStart to digits.Count - 1 do
p += digits[i];
longp := p;
end;
Writeln($' d = {d} период: ', periodLength, ' цифр');
end;
end;
Периодическая таблица заполнена доверху. Публикуем!
Writeln($' Самый длинный период = {long} для числа {longd} =
{longp}');
Writeln;
end;
begin
Writeln(' Project Euler. Problem 26');
WriteLn;
Solve;
end.
Ответ на задачу раскрывает секретную хитрость составителей задачи: простое число 983 даёт периодическую дробь с длиной периода 982 цифры!
163
Project Euler. Problem 26
d
d
d
d
d
d
d
d
d
d
=
=
=
=
=
=
=
=
=
=
...
2
3
4
5
6
7
8
9
10
11
период: 1 цифр
период: 1 цифр
период: 1 цифр
период: 1 цифр
период: 1 цифр
период: 6 цифр
период: 1 цифр
период: 1 цифр
период: 1 цифр
период: 2 цифр
d = 983 период: 982 цифр
d = 984 период: 5 цифр
d = 985 период: 98 цифр
d = 986 период: 112 цифр
d = 987 период: 138 цифр
d = 988 период: 18 цифр
d = 989 период: 462 цифр
d = 990 период: 2 цифр
d = 991 период: 495 цифр
d = 992 период: 15 цифр
d = 993 период: 110 цифр
d = 994 период: 210 цифр
d = 995 период: 99 цифр
d = 996 период: 41 цифр
d = 997 период: 166 цифр
d = 998 период: 498 цифр
d = 999 период: 3 цифр
d = 1000 период: 1 цифр
Самый длинный период = 982 для числа 983 =
0010172939979654120040691759918616480162767039674465920651068158697863682
6042726347914547304170905391658189216683621566632756866734486266531027466
9379450661241098677517802644964394710071210579857578840284842319430315361
1393692777212614445574771108850457782299084435401831129196337741607324516
7853509664292980671414038657171922685656154628687690742624618514750762970
4984740590030518819938962360122075279755849440488301119023397761953204476
0935910478128179043743641912512716174974567650050864699898270600203458799
5930824008138351983723296032553407934893184130213631739572736520854526958
2909460834181078331637843336724313326551373346897253306205493387589013224
8219735503560528992878942014242115971515768056968463886063072227873855544
164
2522889114954221770091556459816887080366225839267548321464903357070193285
8596134282807731434384537131230925737538148524923702950152594099694811800
6103763987792472024415055951169888097660223804679552390640895218718209562
563580874872838250254323499491353
165
Проект Эйлер027
Исходный код программы находится в файле Эйлер027.pas.
Задача 27 (Problem 27) называется Quadratic Primes.
Квадратичные простые числа
166
Вот, наконец, задача, непосредственно связанная с именем Леонарда Эйлера!
Если n = 0, то 0² + a*0 + b = b, поэтому b должно быть простым числом, и
|b| ≤ 1000.
Начинаем решать задачу:
begin
Writeln(' Project Euler. Problem 27');
WriteLn;
Solve;
end.
У нас есть метод расширения для целых чисел IsPrime, поэтому мы быстро
и лихо соберём все простые числа в список primeBs:
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
// лучшие значения:
var maxPrimes := 0;
var bestA := 0;
var bestB := 0;
// все простые числа b в диапазоне [-1000, 1000]:
var primeBs := new List<int>;
for var b := 2 to 1000 do
if b.IsPrime then
primeBs.Add(b);
Writeln($' Простые b в диапазоне [-1000, 1000]: {primeBs.Count}');
Всего таких чисел 336:
Project Euler. Problem 27
Простые b в диапазоне [-1000, 1000]: 168
167
Теперь в списке primeBs надёжно хранятся все возможные значения параметра b. Из условия задачи нам известно, что |a| < 1000.
В двух циклах получаем все возможные пары чисел a и b:
// перебираем все возможные a и b:
for var a := -999 to 999 do begin
for var i := 0 to primeBs.Count - 1 do begin
var b := primeBs[i];
А функции CountPrimes возвращает число последовательных простых по
формуле для текущих значений a и b:
// подсчитываем, простые числа
// для этих значений:
var primesCount := CountPrimes(a, b);
uses MathExtensions;
// СКОЛЬКО ПОСЛЕДОВАТЕЛЬНЫХ ПРОСТЫХ ЧИСЕЛ ДАЕТ ФОРМУЛА N2 + AN + B,
// НАЧИНАЯ С N = 0
function CountPrimes(a, b: integer): integer;
begin
// счётчик:
var n := 0;
while true do begin
var num := n*n + a*n + b;
if not num.IsPrime then break;
n += 1;
end;
// кол-во последовательных простых чисел:
Result := n;
end;
Получив лучшее решение, немедля показываем его себе на экране:
// обновляем рекорд:
if primesCount > maxPrimes then begin
168
maxPrimes := primesCount;
bestA := a;
bestB := b;
// печатаем лучшие результаты:
Writeln($' Новый максимум: a = {a,4}, b = {b,4}, простых чисел: {primesCount,3}, произведение: {a*b,6}');
end;
end;
end;
Наглядно получилось на загляденье:
Новый
Новый
Новый
Новый
Новый
Новый
Новый
Новый
Новый
Новый
Новый
максимум:
максимум:
максимум:
максимум:
максимум:
максимум:
максимум:
максимум:
максимум:
максимум:
максимум:
a
a
a
a
a
a
a
a
a
a
a
=
=
=
=
=
=
=
=
=
=
=
-999,
-996,
-499,
-325,
-245,
-197,
-163,
-131,
-121,
-105,
-61,
b
b
b
b
b
b
b
b
b
b
b
=
=
=
=
=
=
=
=
=
=
=
2,
997,
997,
977,
977,
983,
983,
941,
947,
967,
971,
простых
простых
простых
простых
простых
простых
простых
простых
простых
простых
простых
чисел:
чисел:
чисел:
чисел:
чисел:
чисел:
чисел:
чисел:
чисел:
чисел:
чисел:
1,
2,
3,
4,
5,
6,
7,
8,
9,
11,
71,
произведение:
произведение:
произведение:
произведение:
произведение:
произведение:
произведение:
произведение:
произведение:
произведение:
произведение:
-1998
-993012
-497503
-317525
-239365
-193651
-160229
-123271
-114587
-101535
-59231
В конце списка мы уже видим ответ на задачу, но дополнительно услаждаем
свой взор конкретной информацией, не оставляющей никаких сомнений в
правильности нашего решения задачи:
Writeln;
Writeln('=' * 70);
Writeln(' РЕЗУЛЬТАТ:');
Writeln($' a = {bestA}');
Writeln($' b = {bestB}');
Writeln($' Кол-во последовательных простых чисел = {maxPrimes}');
Writeln($' Произведение a ? b = {bestA * bestB}');
Writeln('=' * 70);
======================================================================
РЕЗУЛЬТАТ:
169
a = -61
b = 971
Кол-во последовательных простых чисел = 71
Произведение a × b = -59231
======================================================================
Для успокоения души проверяем наше решение:
// проверяем решение:
Writeln;
Writeln($' Проверка формулы n? + {bestA}n + {bestB}:');
for var n := 0 to maxPrimes - 1 do begin
var num := n*n + bestA*n + bestB;
Writeln($' n = {n,2}: {n*n,4} + {bestA*n,5} + {bestB,5}
({num.IsPrime})');
end;
end;
= {num,6}
Всё сходится тютелька в тютельку:
Проверка формулы n² + -61n + 971:
n = 0:
0 +
0 +
971 =
n = 1:
1 +
-61 +
971 =
n = 2:
4 + -122 +
971 =
n = 3:
9 + -183 +
971 =
n = 4:
16 + -244 +
971 =
n = 5:
25 + -305 +
971 =
n = 6:
36 + -366 +
971 =
n = 7:
49 + -427 +
971 =
...
n
n
n
n
n
n
n
n
=
=
=
=
=
=
=
=
63:
64:
65:
66:
67:
68:
69:
70:
3969
4096
4225
4356
4489
4624
4761
4900
+
+
+
+
+
+
+
+
-3843
-3904
-3965
-4026
-4087
-4148
-4209
-4270
+
+
+
+
+
+
+
+
971
971
971
971
971
971
971
971
=
=
=
=
=
=
=
=
971
911
853
797
743
691
641
593
(True)
(True)
(True)
(True)
(True)
(True)
(True)
(True)
1097
1163
1231
1301
1373
1447
1523
1601
(True)
(True)
(True)
(True)
(True)
(True)
(True)
(True)
170
Тут даже крот не подкопается!
Проект Эйлер028
Исходный код программы находится в файле Эйлер028.pas.
Задача 28 (Problem 28) называется Number Spiral Diagonals.
171
Диагонали числовой спирали
Начиная с числа 1 и двигаясь дальше вправо по часовой стрелке, образуется
следующая спираль 5 на 5:
21 22 23 24 25
20 7 8 9 10
19 6 1 2 11
18 5 4 3 12
17 16 15 14 13
Можно убедиться, что сумма чисел в диагоналях равна 101.
Какова сумма чисел в диагоналях спирали 1001 на 1001, образованной таким же способом?
Прежде чем браться за решение задачи, давайте пристально посмотрим на
числовую спираль.
Она квадратная, с нечётными сторонами, в центре всегда прочно стоит единица. Длина сторон квадратов – 1, 3, 5, 7, … Нас интересуют числа в вершинах этих квадратов:
Квадрат 0: 1 × 1 → 1
Квадрат 1: 3 × 3 → 3, 5, 7, 9
Квадрат 2: 5 × 5 → 13, 17, 21, 25
Квадрат 3: 7 × 7 → 31, 37, 43, 49
Если n – это номер квадрата, то расстояние между числами в вершинах
квадрата равно 2n.
После таких рассуждений можно смело браться за решение задачи.
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
// размер спирали
var size := 1001;
172
// сумма чисел в вершинах квадрата:
var sum := 1;
// число в вершине квадрата:
var current := 1;
// квадраты:
for var n := 1 to (size - 1) div 2 do begin
// шаг между диагональными числами в одном квадрате:
var step := 2 * n;
// 4 диагональных числа в этом квадрате:
loop 4 do begin
current += step;
sum += current;
end;
end;
Writeln($' Спираль {size} ? {size}');
Writeln($' Сумма чисел по диагоналям: {sum}');
end;
begin
Writeln(' Project Euler. Problem 28');
WriteLn;
Solve;
end.
Спираль закручена – задача раскручена:
Project Euler. Problem 28
Спираль 1001 × 1001
Сумма чисел по диагоналям: 669171001
Проект Спираль Улама
Исходный код программы находится в файле Спираль Улама.pas,
Спираль Улама 2.pas, Спираль Улама 3.pas, Спираль Улама 4.pas.
173
Согласно легенде, американский математик польского происхождения Станислав Улам вычертил числовую спираль на каком-то скучном докладе. Говорят, что это было более 60 лет назад, в 1963 году. Эту числовую загогулину называют спиралью (или скатертью) Улама (Ulam Spiral).
Квадрат Улама из книги Математические досуги Мартина Гарднера
С точки зрения программирования, задача формулируется так:
Начиная с 1 и двигаясь против часовой стрелки по спирали, заполнить квадратную матрицу последовательными числами.
На первый взгляд, спираль Улама ничем не примечательна, но он высмотрел в ней интересные закономерности, связанные с простыми числами (поэтому её называют также Prime Number Spiral). У него это вышло случайно,
когда он обводил кружками простые числа. Поскольку нам уже поздно полагаться на случай, то мы намеренно выкрасим в синий цвет клетки с простыми числами.
Легко заметить, глядючи на рисунок⬆, что эта спираль не отличается от
спирали из задачи Проект Эйлера 028, поэтому мы не станем повторяться с
числами, а нарисуем спираль в клеточку. Ежели закрасить клетки с простыми числами, то диагонали с простыми числами покажут себя во всей
красе.
174
Для вычерчивания спиральных клеточек мы используем модуль
GraphWPF, а для просева простых чисел – наш модуль MathExtensions:
uses GraphWPF, MathExtensions;
Сразу создаём окно надлежащих размеров, вызываем процедуру
DrawUlamSpiral для вычерчивания спирали и назначаем функцию-обработчик нажатия на клавиши:
begin
Window.SetSize(650, 670);
Window.CenterOnScreen;
// спираль по умолчанию:
DrawUlamSpiral(41, 15);
// функция - обработчик клавиш:
OnKeyDown := KeyDown;
// инструкция:
Writeln(' Управление:');
Writeln(' 1 - спираль 21х21');
Writeln(' 2 - спираль 31х31');
Writeln(' 3 - спираль 41х41');
Writeln(' 4 - спираль 51х51');
Writeln(' Esc - выход');
end.
Клавиши 1..4 чертят спирали разного размера:
// ОБРАБОТЧИК НАЖАТИЯ НА КЛАВИШИ
procedure KeyDown(k: Key);
begin
case k of
Key.D1: begin
Window.SetSize(600, 600); DrawUlamSpiral(21, 25); end;
Key.D2: begin
Window.SetSize(600, 610); DrawUlamSpiral(31, 18); end;
Key.D3: begin
175
Window.SetSize(650, 670); DrawUlamSpiral(41, 15); end;
Key.D4: begin
Window.SetSize(700, 690); DrawUlamSpiral(51, 12); end;
Key.Escape: Window.Close;
end;
end;
Самая главная процедура программы – DrawUlamSpiral. Она получает
длину стороны квадратной спирали и размер клеток:
// ЧЕРТИМ СПИРАЛЬ УЛАМА
procedure DrawUlamSpiral(size, cellSize: int);
begin
Window.Clear;
Window.Title := $' Спираль Улама {size} ? {size}';
Затем вычерчивает спираль:
var
var
var
var
spiral := new int[size, size];
x := size div 2;
y := size div 2;
num := 1;
// заполняем спираль:
var dirs := Arr((0,1), (1,0), (0,-1), (-1,0));
var dirIdx := 0;
var steps := 1;
while num <= size*size do begin
spiral[x, y] := num;
for var i := 1 to 2 do begin
var (dx, dy) := dirs[dirIdx];
for var j := 1 to steps do begin
x += dx;
y += dy;
num += 1;
if num <= size*size then
spiral[x, y] := num;
end;
176
dirIdx := (dirIdx + 1) mod 4;
end;
steps += 1;
end;
// спираль:
var offsetX := (Window.Width - size * cellSize) / 2;
var offsetY := (Window.Height - size * cellSize) / 2;
for var i := 0 to size - 1 do
for var j := 0 to size - 1 do begin
var cellX := offsetX + j * cellSize;
var cellY := offsetY + i * cellSize;
// клетка:
Pen.Color := Colors.LightGray;
Brush.Color := Colors.White;
Rectangle(cellX, cellY, cellSize, cellSize);
И закрашивает для нашего удовольствия синим цветом клетки с простыми
числами:
// закрашиваем простые числа:
if spiral[i, j].IsPrime then begin
Brush.Color := Colors.Blue;
FillRectangle(cellX + 1, cellY + 1, cellSize - 2, cellSize - 2);
end;
// печатаем число (для маленьких спиралей):
if cellSize > 20 then begin
Brush.Color := Colors.Transparent;
Font.Size := 8;
TextOut(cellX + 2, cellY + 2, spiral[i, j].ToString);
end;
end;
Brush.Color := Colors.White;
Font.Size := 12;
TextOut(10, Window.Height - 20, 'Синие клетки - простые числа');
end;
177
Протяжённость и обилие диагоналей зависят от размера спирали, так что
вы можете смело и умело экспериментировать с этой штуковиной!
Для любителей и знатоков числовой изящности пишем разноцветную программу:
178
uses GraphWPF, Timers, MathExtensions;
var
currentSize: int;
cellSize: int;
showNumbers: bool := true;
// 0 - синий, 1 - радуга, 2 - тепловая карта
colorMode: int := 0;
// ВОЗВРАЩАЕТ ЦВЕТ КЛЕТОК
function GetPrimeColor(n: integer): Color;
begin
case colorMode of
0: Result := Colors.Blue;
1: Result := Color.FromRgb(Random(256), Random(256), Random(256));
// тепловая карта: чем больше n, тем "теплее" цвет
2: begin
var intensity := Min(255, n div 10);
Result := Color.FromRgb(255, 255 - intensity, 255 - intensity);
end;
else Result := Colors.Blue;
end;
end;
// ЧЕРТИТ СПИРАЛЬ УЛАМА
procedure DrawUlamSpiral(size: integer);
begin
currentSize := size;
cellSize := Round(Min(Window.Width, Window.Height) / size) - 2;
Window.Clear(Colors.White);
Window.Title := $' Спираль Улама {size} ? {size} (цвет: ' +
if colorMode = 0 then 'синий)' else if colorMode = 1
then 'радуга)' else 'тепло)';
var spiral := new integer[size, size];
var x := size div 2;
var y := size div 2;
179
var num := 1;
// заполняем спираль:
var dirs := Arr((0,1), (1,0), (0,-1), (-1,0));
var dirIdx := 0;
var steps := 1;
while num <= size*size do begin
spiral[x, y] := num;
for var i := 1 to 2 do begin
var (dx, dy) := dirs[dirIdx];
for var j := 1 to steps do begin
x += dx;
y += dy;
num += 1;
if num <= size*size then
spiral[x, y] := num;
end;
dirIdx := (dirIdx + 1) mod 4;
end;
steps += 1;
end;
// рисуем спираль:
var offsetX := (Window.Width - size * cellSize) / 2;
var offsetY := (Window.Height - size * cellSize) / 2;
for var i := 0 to size - 1 do
for var j := 0 to size - 1 do begin
var cellX := offsetX + j * cellSize;
var cellY := offsetY + i * cellSize;
var number := spiral[i, j];
// рисуем клетку:
Pen.Color := Colors.LightGray;
Brush.Color := Colors.White;
Rectangle(cellX, cellY, cellSize, cellSize);
// закрашиваем простые числа:
if number.IsPrime then begin
Brush.Color := GetPrimeColor(number);
FillRectangle(cellX + 1, cellY + 1, cellSize - 2, cellSize
- 2);
end;
180
// печатаем число:
if showNumbers and (cellSize > 15) then begin
Brush.Color := Colors.Transparent;
Font.Size := Max(6, cellSize div 3);
var textColor := if number.IsPrime then Colors.White else
Colors.Black;
DrawText(cellX + 2, cellY + 2, cellSize - 4, cellSize - 4,
number, textColor);
end;
end;
// считаем простые числа:
var primeCount := 0;
for var i := 0 to size - 1 do
for var j := 0 to size - 1 do
if spiral[i, j].IsPrime then
primeCount += 1;
// надписи:
Brush.Color := Colors.Transparent;
Font.Size := 14;
TextOut(10, 0, $' Спираль Улама {size} ? {size}');
Font.Size := 12;
TextOut(10, 22, $' Простых чисел: {primeCount} ({Round((primeCount *
100 / (size*size)), 2)}%)');
TextOut(10, Window.Height - 30, 'Управление: 1-4 размер, C - цвета, N
- числа, Esc - выход');
end;
procedure KeyDown(k: Key);
begin
case k of
Key.D1: begin
Window.SetSize(500, 500); DrawUlamSpiral(21); end;
Key.D2: begin
Window.SetSize(600, 600); DrawUlamSpiral(31); end;
Key.D3: begin
Window.SetSize(700, 700); DrawUlamSpiral(41); end;
Key.D4: begin
Window.SetSize(800, 800); DrawUlamSpiral(51); end;
Key.D5: begin
Window.SetSize(1000, 960); DrawUlamSpiral(71); end;
181
Key.C: begin
colorMode := (colorMode + 1) mod 3;
DrawUlamSpiral(currentSize); end;
Key.N: begin
showNumbers := not showNumbers;
DrawUlamSpiral(currentSize); end;
Key.Escape: Window.Close;
end;
end;
begin
Window.SetSize(700, 700);
Window.CenterOnScreen;
// начальная спираль:
DrawUlamSpiral(41);
// обработчик нажатия на клавиши:
OnKeyDown := KeyDown;
// инструкция в консоли:
Writeln(' Управление:');
Writeln(' 1 - спираль 21?21');
Writeln(' 2 - спираль 31?31');
Writeln(' 3 - спираль 41?41');
Writeln(' 4 - спираль 51?51');
Writeln(' 5 - спираль 71?71');
Writeln(' C - переключение цветовой схемы');
Writeln(' N - показать/скрыть числа');
Writeln(' Esc - выход');
end.
Не побоюсь сказать: то, что я вижу, получилось чертовски изящно!
182
Желающие понаблюдать за процессом верчения спирали могут это сделать
со следующей программой:
uses GraphWPF, Timers, MathExtensions;
var
183
// таймер:
tim: Timer;
currentStep: integer;
maxSteps: integer;
spiral: array [,] of integer;
size: integer := 41;
// отслеживаем построенные ячейки
builtSpiral: array [,] of boolean;
// ГОТОВИМ СПИРАЛЬ
procedure InitializeSpiral;
begin
spiral := new int[size, size];
builtSpiral := new bool[size, size];
var x := size div 2;
var y := size div 2;
var num := 1;
var dirs := Arr((0,1), (1,0), (0,-1), (-1,0));
var dirIdx := 0;
var steps := 1;
// вычисляем все клетки спирали:
while num <= size*size do begin
spiral[x, y] := num;
for var i := 1 to 2 do begin
var (dx, dy) := dirs[dirIdx];
for var j := 1 to steps do begin
x += dx;
y += dy;
num += 1;
if num <= size*size then
spiral[x, y] := num;
end;
dirIdx := (dirIdx + 1) mod 4;
end;
steps += 1;
end;
maxSteps := size * size;
currentStep := 0;
end;
184
// РИСУЕМ КЛЕТКУ
procedure DrawCell(i, j: integer);
begin
var cellSize := Round(Min(Window.Width, Window.Height) / size) - 2;
var offsetX := (Window.Width - size * cellSize) / 2;
var offsetY := (Window.Height - size * cellSize) / 2;
var cellX := offsetX + j * cellSize;
var cellY := offsetY + i * cellSize;
var number := spiral[i, j];
// рисуем клетку:
Pen.Color := Colors.LightGray;
Brush.Color := Colors.White;
Rectangle(cellX, cellY, cellSize, cellSize);
// закрашиваем простые числа:
if (number.IsPrime) then begin
Brush.Color := Colors.Blue;
FillRectangle(cellX + 1, cellY + 1, cellSize - 2, cellSize - 2);
end;
// печатаем число:
if cellSize > 15 then begin
Brush.Color := Colors.Transparent;
Font.Size := Max(6, cellSize div 3);
var textColor := if (number.IsPrime) then Colors.White else Colors.Black;
DrawText(cellX + 2, cellY + 2, cellSize - 4, cellSize - 4,
number.ToString, textColor);
end;
end;
// АНИМАЦИЯ
procedure DrawStep;
begin
if currentStep >= maxSteps then begin
tim.Stop;
exit;
end;
// координаты текущего числа в спирали:
var found := false;
var ci := 0;
185
var cj := 0;
for var i := 0 to size - 1 do begin
for var j := 0 to size - 1 do begin
if spiral[i, j] = currentStep + 1 then begin
ci := i;
cj := j;
found := true;
break;
end;
end;
if found then break;
end;
// рисуем одну ячейку:
if found and not builtSpiral[ci, cj] then begin
DrawCell(ci, cj);
builtSpiral[ci, cj] := true;
end;
currentStep += 1;
end;
// НАЧИНАЕМ АНИМАЦИЮ
procedure StartAnimation(spiralSize: int);
begin
if tim <> nil then
tim.Stop;
size := spiralSize;
InitializeSpiral;
// очищаем окно:
Window.Clear(Colors.White);
// рисуем сетку
var cellSize := Round(Min(Window.Width, Window.Height) / size) - 2;
var offsetX := (Window.Width - size * cellSize) / 2;
var offsetY := (Window.Height - size * cellSize) / 2;
// рисуем пустую сетку:
for var i := 0 to size - 1 do
for var j := 0 to size - 1 do begin
var cellX := offsetX + j * cellSize;
var cellY := offsetY + i * cellSize;
186
Pen.Color := Colors.LightGray;
Brush.Color := Colors.White;
Rectangle(cellX, cellY, cellSize, cellSize);
end;
// запускаем таймер:
tim := new Timer(5, DrawStep);
tim.Start;
end;
// ПРОЦЕДУРА ОБРАБОТКИ НАЖАТИЯ НА КЛАВИШИ
procedure KeyDown(k: Key);
begin
case k of
Key.D1: begin Window.SetSize(500, 500); StartAnimation(21);
Key.D2: begin Window.SetSize(600, 600); StartAnimation(31);
Key.D3: begin Window.SetSize(700, 700); StartAnimation(41);
Key.D4: begin Window.SetSize(800, 800); StartAnimation(51);
Key.Space: if tim <> nil then
if tim.Enabled then tim.Stop else tim.Start;
// перезапуск анимации:
Key.R: if tim <> nil then StartAnimation(size);
Key.Escape: Window.Close;
end;
end;
end;
end;
end;
end;
begin
Window.SetSize(700, 700);
Window.CenterOnScreen;
Window.Title := ' Анимированная спираль Улама';
// запускаем анимацию:
StartAnimation(41);
// обработчик нажатия на клавиши:
OnKeyDown := KeyDown;
Writeln('
Writeln('
Writeln('
Writeln('
Управление:');
1-4 - выбрать размер спирали');
Пробел - пауза/продолжить');
R - перезапуск анимации');
187
Writeln(' Esc - выход');
end.
Спираль крутится, вертится, но не хочет упасть:
Станислав Улам от скуки открыл удивительное свойство простых чисел – располагаться в спирали по диагоналям. Тут, конечно, можно подумать, что в любой таблице будет наблюдаться такое же природное числовое явление. Но это
не так. Следующая программа наглядно показывает, что простые числа не такие уж простые, как это может показаться:
188
Приверженцы самостоятельного, критического мышления могут воспользоваться этой программой, чтобы усовершенствовать её до полного недоразумения:
uses GraphWPF, MathExtensions;
// СРАВНИВАЕМ!
procedure DrawComparison;
begin
Window.SetSize(1200, 600);
Window.CenterOnScreen;
Window.Title := 'Сравниваем спирали: Улама и обычную';
var size := 15;
var cellSize := 20;
//спираль Улама:
var offsetX1 := 50;
var offsetY1 := 50;
var ulamSpiral := new int[size, size];
189
var x := size div 2;
var y := size div 2;
var num := 1;
var dirs := Arr((0,1), (1,0), (0,-1), (-1,0));
var dirIdx := 0;
var steps := 1;
while num <= size*size do begin
ulamSpiral[x, y] := num;
for var i := 1 to 2 do begin
var (dx, dy) := dirs[dirIdx];
for var j := 1 to steps do begin
x += dx;
y += dy;
num += 1;
if num <= size*size then
ulamSpiral[x, y] := num;
end;
dirIdx := (dirIdx + 1) mod 4;
end;
steps += 1;
end;
// обычная спираль:
var offsetX2 := 650;
var offsetY2 := 50;
var regularSpiral := new int[size, size];
num := 1;
for var i := 0 to size - 1 do
for var j := 0 to size - 1 do begin
regularSpiral[i, j] := num;
num += 1;
end;
// рисуем обе спирали:
for var part := 0 to 1 do begin
var offsetX := if part = 0 then offsetX1 else offsetX2;
var offsetY := offsetY1;
var spiral := if part = 0 then ulamSpiral else regularSpiral;
var title := if part = 0 then ' Спираль Улама' else ' Обычная спираль (по
строкам)';
// надписи:
Font.Size := 16;
TextOut(offsetX, offsetY - 30, title);
190
for var i := 0 to size - 1 do
for var j := 0 to size - 1 do begin
var cellX := offsetX + j * cellSize;
var cellY := offsetY + i * cellSize;
var number := spiral[i, j];
// рисуем клетку:
Pen.Color := Colors.LightGray;
Brush.Color := Colors.White;
Rectangle(cellX, cellY, cellSize, cellSize);
// закрашиваем простые числа
if number.IsPrime then begin
Brush.Color := Colors.Blue;
FillRectangle(cellX + 1, cellY + 1, cellSize - 2, cellSize - 2);
end;
// печатаем число:
Brush.Color := Colors.Transparent;
Font.Size := 8;
var textColor := if number.IsPrime then Colors.White else Colors.Black;
DrawText(cellX + 2, cellY + 2, cellSize - 4, cellSize - 4,
number.ToString, textColor);
end;
end;
// надписи:
Font.Size := 12;
TextOut(50, 400, 'В спирали Улама простые числа образуют диагональные линии.');
TextOut(50, 420, 'В обычной спирали распределение случайное.');
// статистика:
var ulamPrimes := 0;
var regularPrimes := 0;
for var i := 0 to size - 1 do
for var j := 0 to size - 1 do begin
if ulamSpiral[i, j].IsPrime then ulamPrimes += 1;
if regularSpiral[i, j].IsPrime then regularPrimes += 1;
end;
TextOut(50, 470, ' Статистика для спирали 15?15:');
TextOut(50, 490, $' Спираль Улама: {ulamPrimes} простых чисел');
TextOut(50, 510, $' Обычная спираль: {regularPrimes} простых чисел');
TextOut(50, 530, ' (количество совпадает, но распределение разное!)');
end;
begin
DrawComparison;
end.
191
192
Проект Эйлер029
Исходный код программы находится в файле Эйлер029.pas.
Задача 29 (Problem 29) называется Distinct Powers.
Различные степени
Рассмотрим все целочисленные комбинации ab для 2 ≤ a ≤ 5 и 2 ≤ b ≤ 5:
22=4, 23=8, 24=16, 25=32
32=9, 33=27, 34=81, 35=243
42=16, 43=64, 44=256, 45=1024
52=25, 53=125, 54=625, 55=3125
Если их расположить в порядке возрастания, исключив повторения, мы получим
следующую последовательность из 15 различных членов:
4, 8, 9, 16, 25, 27, 32, 64, 81, 125, 243, 256, 625, 1024, 3125
Сколько различных членов имеет последовательность ab для 2 ≤ a ≤ 100 и
2 ≤ b ≤ 100?
193
Перебор для чисел a и b невелик, поэтому решаем задачу методом грубой
силы.
Всего существует комбинаций 99 × 99 = 9801 чисел a и b. Решение задачи
покажет, что из них 9183 уникальные, а 618 – повторяются. Например, 8² =
2⁶, 2100 = 450, 1625 = 2100. Поэтому мы поместим все степени в множество,
чтобы в нём остались только единичные экземпляры всех степеней:
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
var hs := new HashSet<BigInteger>;
В двух циклах for перебираем все комбинации чисел a и b и добавляем степени в множество:
for var a := 2 to 100 do
for var b := 2 to 100 do
hs.Add(BigInteger.Pow(a, b));
Печатаем число элементов в множестве – и задача решена:
Writeln($' Всего членов в последовательности: {hs.Count}');
// 9183
end;
begin
Writeln(' Project Euler. Problem 29');
WriteLn;
Solve;
end.
На деле это выглядит так:
Project Euler. Problem 29
194
Всего членов в последовательности: 9183
Проект Эйлер030
Исходный код программы находится в файле Эйлер030.pas.
Задача 30 (Problem 30) называется Digit Fifth Powers.
Пятые степени цифр
Удивительно, но существует только три числа, которые могут быть записаны в
виде суммы четвертых степеней их цифр:
1634 = 14 + 64 + 34 + 44
8208 = 84 + 24 + 04 + 84
9474 = 94 + 44 + 74 + 44
1 = 14 не считается, так как это - не сумма.
Сумма этих чисел равна 1634 + 8208 + 9474 = 19316.
Найдите сумму всех чисел, которые могут быть записаны в виде суммы
пятых степеней их цифр.
195
В лучшем случае все числа состоят из девяток, пятые степени которых
равны 9⁵ = 59049.
Но в числах наверняка объявятся и другие цифры. Чтобы не вычислять пятые степени цифр каждого числа, мы сразу поместим их в массив:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
// вычисляем пятые степени:
var p5 := new int[10];
for var d := 0 to 9 do
p5[d] := int(d ** 5);
Мы вполне разумно можем предположить, что наибольшее число не превосходит 354294:
// сумма:
var sum := 0;
// верхняя граница: 6 х 9⁵ = 354294
for var n := 10 to 354294 do begin
var s := 0;
var temp := n;
Находим сумму пятых степеней цифр текущего числа:
while temp > 0 do begin
s += p5[temp mod 10];
temp := temp div 10;
end;
Если она совпадает самим числом, то добавляем его к результирующей
сумме:
196
if s = n then
sum += n;
end;
Перебор закончен. Печатаем ответ:
Writeln($' Сумма чисел, равных сумме пятых степеней их цифр: {sum}');
// 443839
end;
begin
Writeln(' Project Euler. Problem 30');
WriteLn;
Solve;
end.
Ответ вот такой и правильный:
Project Euler. Problem 30
Сумма чисел, равных сумме пятых степеней их цифр: 443839
Любители математики задолго до нас нашли все числа, пятые степени цифр
которых равны исходным числам:
4150
4151
54748
92727
93084
194979
=
=
=
=
=
=
4⁵
4⁵
5⁵
9⁵
9⁵
1⁵
+
+
+
+
+
+
1⁵
1⁵
4⁵
2⁵
3⁵
9⁵
+
+
+
+
+
+
5⁵
5⁵
7⁵
7⁵
0⁵
4⁵
+
+
+
+
+
+
0⁵
1⁵
4⁵
2⁵
8⁵
9⁵
+
+
+
+
8⁵
7⁵
4⁵
7⁵ + 9⁵
197
Проект Эйлер031
Исходный код программы находится в файле Эйлер031.pas.
Задача 31 (Problem 31) называется Coin Sums.
In the United Kingdom the currency is made up of pound (£) and pence (p). There are
eight coins in general circulation:
1p, 2p, 5p, 10p, 20p, 50p, £1 (100p), and £2 (200p).
It is possible to make £2 in the following way:
1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p
How many different ways can £2 be made using any number of coins?
Суммы монет
В Англии валютой являются фунты стерлингов £ и пенсы p, и в обращении есть
восемь монет:
1p, 2p, 5p, 10p, 20p, 50p, £1 (100p) и £2 (200p).
£2 возможно составить следующим образом:
1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p
Сколькими разными способами можно составить £2, используя любое количество монет?
Из условия задачи сразу проклёвывается динамическое программирование.
Начинаем динамично программировать:
198
begin
Writeln(' Project Euler. Problem 31');
WriteLn;
Solve;
end.
Кладём все фунтопенсы в массивный кошелёк с целью набрать всеми способами 2 фунта пенсов:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
// номиналы монет в пенсах:
var coins := |1, 2, 5, 10, 20, 50, 100, 200|;
var target := 200; // 2 фунта = 200 пенсов
Для динамического программирования нужен свой массив, в который мы
сразу отправляем первый способ – как набрать 0 пенсов:
// массив для динамического программирования -->
// ways[i] - количество способов набрать сумму i пенсов:
var ways := new int64[target + 1];
// 1 способ набрать 0 пенсов - не брать монеты:
ways[0] := 1;
Заполняем массив ways монетами, как это принято в динамическом программировании, отталкиваясь от первой записи⬆:
// динамическое программирование:
foreach var coin in coins do begin
for var amount := coin to target do
199
ways[amount] += ways[amount - coin];
end;
В массиве ways индекс target как раз и показывает ответ на задачу:
Writeln($' Количество способов = {ways[target]}');
// 73682
end;
Все способы аккуратно пересчитаны:
Project Euler. Problem 31
Количество способов = 73682
Проект Эйлер032
Исходный код программы находится в файле Эйлер032.pas.
Задача номер 32 (Problem 132) называется Pandigital Products.
Пандигитальные произведения
200
Каждое n-значное число, которое содержит каждую цифру от 1 до n ровно
один раз, будем считать пандигитальным; к примеру, 5-значное число 15234
является пан-цифровым, так как. содержит цифры от 1 до 5.
Произведение 7254 является необычным, поскольку равенство 39 × 186 =
7254, состоящее из множимого, множителя и произведения является панцифровым, то есть. содержит цифры от 1 до 9.
Найдите сумму всех пандигитальных произведений, для которых равенство
"множимое × множитель = произведение" можно записать цифрами от 1 до
9, используя каждую цифру только один раз.
ПОДСКАЗКА: Некоторые произведения можно получить несколькими способами, поэтому убедитесь, что включили их в сумму лишь единожды.
Сегодня наш выбор пал на задачу 32, которая называется Pandigital
Products, или Пандигитальные произведения. С этой задачей справились
уже менее восьмидесяти тысяч участников, то есть задача не столь простая,
как предыдущие.
И тут вы должны непременно внимательно прочитать условие задачи,
чтобы узнать и понять, что такое пандигитальные числа.
В общем случае это числа, которые состоят из разных цифр. По условию
задачи, мы должны найти сумму всех произведений. При этом оба сомножителя и само произведение должны состоять из разных цифр, а именно от
единицы до девятки, но без нуля.
После некоторых сугубых рассуждений мы должны прийти к выводу, что
два сомножителя и произведение должны состоять из девяти разных
цифр. Такое дело может случиться, если первый сомножитель однозначный, а второй – четырёхзначный, или первый сомножитель двузначный, а
второй – трёхзначный. В любом случае произведение состоит из четырёх
разных цифр.
Из однозначных мы должны отбросить единицу, которая порождает произведение с теми же цифрами, что и у второго сомножителя. Остаются числа
2..9. Первый сомножитель может быть также двузначным числом 11..99.
201
Таким образом, в первом цикле for мы изменяем значение первого сомножителя от 2 до 99:
uses MathExtensions;
// СТРОКА СОСТОИТ ИЗ РАЗНЫХ ЦИФР?
function IsPandigital(s : string) : bool;
begin
Result := (s.Length = 9) and
(not s.Contains('0')) and
(s.Distinct().Count() = 9);
end;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// сумма произведений:
var sum: int64 := 0;
// множество для отсеивания повторяющихся произведений:
var hs: HashSet<int> := [];
// решаем задачу -->
// перебираем первые сомножители:
for var n1 := 2 to 99 do begin
Если первый сомножитель однозначный, то второй - четырёхзначный, а
если первый сомножитель двузначный, то второй - трёхзначный. Наименьшее пандигитальное трёхзначное число - 123, а четырёхзначное – 1234.
Пределы изменения и второго сомножителя найдены:
// второй сомножитель:
var n2min := (n1 > 9) ? 123 : 1234;
var n2max := 9999 div n1;
// перебираем вторые сомножители:
for var n2 := n2min to n2max do begin
202
Проще всего провести проверки на пандигитальность не с числами, а со
строками, поэтому преобразуем оба сомножителя и произведение в одну
строку:
// находим произведение:
var product := n1 * n2;
// составляем строку из трёх чисел:
var s := '' + n1 + n2 + product;
Проверку на пандигитальность проводим в функции IsPandigital.
// в строке есть повторы?
if not IsPandigital(s) then continue;
Хотя в задаче требуется найти только сумму чисел, мы распечатываем все
годные произведения на экране:
// для информации печатаем правильные произведения:
Println($' {n1,2} x {n2,4} = {product,5}');
Чтобы проверить, встречалось ли уже очередное произведение раньше, используем множество:
// произведения не должны повторяться:
if hs.Contains(product) then continue;
// добавляем в множество новое произведение:
hs.Add(product);
// добавляем произведение в сумму:
sum += product;
end;
end;
// печатаем ответ:
Println($' Сумма равна: {sum}');
WriteLn;
end;
203
begin
Writeln(' Project Euler. Problem 32');
WriteLn;
Solve();
end.
Запускаем нашу программу во все тяжкие вычисления сумм и произведений.
И она, покладисто урча выдаёт нам все страшные тайны этой эйлеровской
задачи! И даже - сумму всех пандигитальных произведений!
Project Euler. Problem 32
4
4
12
18
27
28
39
42
48
x 1738 =
x 1963 =
x 483 =
x 297 =
x 198 =
x 157 =
x 186 =
x 138 =
x 159 =
6952
7852
5796
5346
5346
4396
7254
5796
7632
Сумма равна: 45228
И отдыхаем, как
Уральские пельмени.
204
Всем известны плоские кукурузные хлопья, которые забавно хрустят на
зубах со времён нашего детства, когда зубы были большими.
А теперь зубы маленькие и хрустят уже безо всяких дополнительных кукурузных хлопьев.
И вот для таких малозубых, но алчных до кукурузных хлопьев людей разнополого возраста, всем никому неизвестная компашка Золотое зерно выпустила Кукурузные хлопцы – для тех, у кого есть перевес недовеса.
205
Проект Эйлер033
Исходный код программы находится в файле Эйлер033.pas.
Задача 33 (Problem 33) называется Digit Cancelling Fractions.
Необычное сокращение дробей
Дробь 49/98 является любопытной, поскольку неопытный математик, пытаясь сократить ее, будет ошибочно полагать, что 49/98 = 4/8, являющееся истиной, получено вычеркиванием девяток.
Дроби вида 30/50 = 3/5 будем считать тривиальными примерами.
Существует ровно 4 нетривиальных примера дробей подобного типа, которые
меньше единицы и содержат двухзначные числа как в числителе, так и в знаменателе.
Пусть произведение этих четырех дробей дано в виде несократимой дроби (числитель и знаменатель дроби не имеют общих сомножителей). Найдите знаменатель этой дроби.
Из условия задачи следует, что мы должны найти все дроби вида ab/cd, в
которых:
• a, b, c, d — цифры (0-9)
206
• a < c (дробь меньше 1)
• b может быть равно d (возможно сокращение)
• Если убрать общую цифру, получится эквивалентная дробь
Вот любопытные дроби, которые нам предстоит найти:
16/64 = 1/4
19/95 = 1/5
26/65 = 2/5
49/98 = 1/2
Как обычно, решение задачи начинается в процедуре Solve:
begin
Writeln(' Project Euler. Problem 33');
WriteLn;
Solve;
end.
Функция IsCuriousFraction проверяет дробь, заданную цифрами:
uses MathExtensions;
// ПРОВЕРЯЕМ ДРОБЬ ВИДА ab/cd
function IsCuriousFraction(a, b, c, d: int): bool;
begin
// избегаем деления на 0:
if (b = 0) or (d = 0) then exit(false);
// тривиальные случаи:
if (a = b) or (c = d) then exit(false);
// числитель:
var originalNumerator := 10*a + b;
// знаменатель:
var originalDenominator := 10*c + d;
// дробь должна быть < 1:
207
if originalNumerator >= originalDenominator then
exit(false);
// дробь:
var originalValue := originalNumerator / originalDenominator;
// пробуем сократить, если есть общая цифра
var simplifiedValue: real;
// сокращаем первую цифру (a и c):
if (a = c) then
simplifiedValue := b / d
// сокращаем a и d:
else if (a = d) then
simplifiedValue := b / c
// сокращаем b и c:
else if (b = c) then
simplifiedValue := a / d
// сокращаем b и d:
else if (b = d) then
simplifiedValue := a / c
// нет общих цифр:
else exit(false);
// проверяем дроби на равенство:
//Result := Abs(originalValue - simplifiedValue) < 1e-10;
Result := originalValue = simplifiedValue;
end;
В процедуре Solve мы ищем дроби, удовлетворяющие условию задачи, и отправляем их на хранение в список curiousFractions:
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
// список дробей:
var curiousFractions := new List<(int, int, int, int)>;
// перебираем все возможные дроби ab/cd:
for var a := 1 to 9 do
for var b := 0 to 9 do
for var c := a + 1 to 9 do // a < c, чтобы дробь была < 1
for var d := 0 to 9 do begin
208
if IsCuriousFraction(a, b, c, d) then
curiousFractions.Add((a, b, c, d));
end;
Все дроби найдены. Аккуратно и бережно печатаем их на экране:
Writeln($' Найдено любопытных дробей: ', curiousFractions.Count);
var productNumerator := 1;
var productDenominator := 1;
foreach var (a, b, c, d) in curiousFractions do begin
var num := 10*a + b;
var den := 10*c + d;
var val := num / den;
var s: String;
if a = c then s := $'{b}/{d}'
else if a = d then s := $'{b}/{c}'
else if b = c then s := $'{a}/{d}'
else s := $'{a}/{c}';
Writeln($' {num}/{den} = {s}');
productNumerator *= num;
productDenominator *= den;
end;
// сокращаем произведение:
var gcdValue := Gcd(productNumerator, productDenominator);
var simplifiedNum := productNumerator div gcdValue;
var simplifiedDen := productDenominator div gcdValue;
Writeln;
Writeln($' Произведение всех дробей = {productNumerator}/{productDenominator}');
Writeln($' После сокращения: {simplifiedNum}/{simplifiedDen}');
Writeln($' Знаменатель дроби = {simplifiedDen}');
// 100
end;
Под барабанную дробь дробно дробим ответ:
209
Project Euler. Problem 33
Найдено
16/64 =
19/95 =
26/65 =
49/98 =
любопытных дробей: 4
1/4
1/5
2/5
4/8
Произведение всех дробей = 387296/38729600
После сокращения: 1/100
Знаменатель дроби = 100
Проект Эйлер034
Исходный код программы находится в файле Эйлер034.pas.
Задача 34 (Problem 34) называется Digit Factorials.
Факториалы цифр
145 является любопытным числом, поскольку 1! + 4! + 5! = 1 + 24 + 120 = 145.
Найдите сумму всех чисел, каждое из которых равно сумме факториалов своих
цифр.
Примечание: поскольку 1! = 1 и 2! = 2 не являются суммами, учитывать их не
следует.
210
Для больших чисел сумма факториалов цифр не успевает за ростом самих
чисел, поэтому это редкое свойство имеют всего 4 числа в десятичной системе.
1 = 1!
2 = 2!
145 = 1! + 4! + 5! = 1 + 24 + 120 = 145
40585 = 4! + 0! + 5! + 8! + 5! = 24 + 1 + 120 + 40320 + 120 = 40585
По условию задачи, первые два числа использовать нельзя, а второй дуэт из
квартета даёт сумму:
145 + 40585 = 40730
Это и есть ответ на задачу, которую нам предстоит решить.
В процедуре Solve сразу заполняем список fact факториалами цифр 0..9:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
// факториалы цифр:
var fact := GetFactorialN(10).ToList;
Для этого я добавил в модуль MathExtensions новый генератор факториалов:
function GetFactorialN(num: int): sequence of Integer;
begin
yield 1;
var n := 1;
foreach var i in Range(1, num) do begin
n := i * n;
yield n;
end;
end;
211
Наука математики говорит, что достаточно искать числа в диапазоне от 10
до 2540160. Нехотя согласимся, а если что-то пойдёт супротив науки, то
расширим диапазон границ поиска вправо до упора:
// сумма:
var sum := 0;
// верхняя граница: 7 х 9! = 2540160:
for var n := 10 to 2540160 do begin
Максимальный вклад одной цифры: 9! = 362880
Для n-значного числа: максимальная сумма = n × 362880
Находим, когда n × 362880 < 10ⁿ⁻¹:
n=7: 7 × 362880 = 2,540,160 < 10,000,000
n=8: 8 × 362880 = 2,903,040 < 100,000,000
Практически достаточно искать до 10⁶
В цикле for вычисляем сумму факториалов s очередного числа n:
var s := int64(0);
var temp := n;
while temp > 0 do begin
s += fact[temp mod 10];
temp := temp div 10;
end;
Если сумма факториалов цифр равна самому числу, то добавляем его к результирующей сумме:
if s = n then
sum += n;
end;
Задача решена. Печатаем ответ:
212
Writeln($' Сумма чисел, равных сумме факториалов их цифр, = {sum}');
// 40730
end;
begin
Writeln(' Project Euler. Problem 34');
WriteLn;
Solve;
end.
Как говорил Винни, сходится и хорошо выходится:
Project Euler. Problem 34
Сумма чисел, равных сумме факториалов их цифр, = 40730
213
Проект Эйлер035
Исходный код программы находится в файле Эйлер035.pas.
Задача 35 (Problem 35) называется Circular Primes.
Круговые простые числа
Число 197 называется круговым простым числом, потому что все перестановки
его цифр с конца в начало являются простыми числами: 197, 719 и 971.
Существует тринадцать таких простых чисел меньше 100: 2, 3, 5, 7, 11, 13, 17, 31,
37, 71, 73, 79 и 97.
Сколько существует круговых простых чисел меньше миллиона?
Число называется круговым простым, если все числа, полученные циклическим сдвигом его цифр, также являются простыми. Например:
197 → 971 → 719 → все простые.
Однозначные простые числа - 2, 3, 5, 7 - формально круговые.
Многозначные простые числа не могут содержать цифры 0, 2, 4, 5, 6, 8.
При повороте такая цифра окажется последней, и число разделится на 2 или
5.
Поэтому многозначные простые числа состоят только из цифр 1, 3, 7, 9.
Начинаем решать задачу приступом:
214
begin
Writeln(' Project Euler. Problem 35');
WriteLn;
Solve;
end.
В процедуре Solve считаем простые круговые числа и отправляем их в список circularPrimes:
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
var limit := 1_000_000;
var count := 0;
var circularPrimes := new List<int>;
Для проверки чисел из заданного диапазона на простоту и «круглость» пишем 2 вспомогательные функции:
uses MathExtensions;
function GetRotations(n: int): array of int;
begin
var s := n.ToString;
SetLength(Result, s.Length);
for var i := 0 to s.Length - 1 do begin
var rotated := s.Substring(i) + s.Substring(0, i);
Result[i] := StrToInt(rotated);
end;
end;
function IsCircularPrime(n: int): bool;
begin
var rotations := GetRotations(n);
foreach var rot in rotations do
215
if not (rot.IsPrime) then
exit(false);
Result := true;
end;
Первая функция циклически переставляет цифры в числе, а вторая проверяет, получилось при этом простое число.
С помощью этих функций мы проверяем все числа в заданном диапазоне,
считаем подходящие и запоминаем их в списке circularPrimes:
// проверяем все числа:
for var n := 2 to limit - 1 do begin
if IsCircularPrime(n) then begin
count += 1;
circularPrimes.Add(n);
end;
end;
Для интереса любопытства распечатываем все круговые простые числа и
ответ на задачу:
// печатаем найденные числа:
foreach var i in circularPrimes do begin
Writeln(i:6);
end;
Writeln;
Writeln($' Всего круговых простых чисел < {limit}: {count}');
// 55
end;
Вот полный отчёт о проделанной работе:
Project Euler. Problem 35
216
2
3
5
7
11
13
17
31
37
71
73
79
97
113
131
197
199
311
337
373
719
733
919
971
991
1193
1931
3119
3779
7793
7937
9311
9377
11939
19391
19937
37199
39119
71993
91193
93719
93911
99371
193939
199933
319993
331999
391939
393919
919393
933199
939193
939391
993319
999331
Всего круговых простых чисел < 1000000: 55
Если из прихоти поискать круговые простые числа до 10 миллионов, то нет.
Проект Эйлер036
Исходный код программы находится в файле Эйлер036.pas.
Задача 36 (Problem 36) называется Double-base Palindromes.
Двухосновные палиндромы
217
Десятичное число 585 = 10010010012 (в двоичной системе), является палиндромом по обоим основаниям.
Найдите сумму всех чисел меньше миллиона, являющихся палиндромами по основаниям 10 и 2.
(Пожалуйста, обратите внимание на то, что палиндромы не могут начинаться с
нуля ни в одном из оснований).
Число, которое является палиндромом как в десятичной, так и в двоичной
системе счисления, называется двухосновным палиндромом. Например:
Десятичное | Двоичное
-----------|---------1
= 1
3
= 11
5
= 101
7
= 111
9
= 1001
33
= 100001
99
= 1100011
313
= 100111001
585
= 1001001001
Не раздвояясь и не размениваясь по мелочам, приступаем к решению задачи:
begin
Writeln(' Project Euler. Problem 36');
WriteLn;
Solve;
end.
В модуле MathExtensions на этот случай у нас есть готовый метод расширения для целых чисел IsPalindrome:
218
function IsPalindrome(Self: int): bool; extensionmethod;
begin
// избавляемся от чисел, заканчивающихся на нуль:
if ((self <> 0) and (self mod 10 = 0)) then
exit(false);
var rev := 0;
var num := self;
while (num >= rev) do begin
rev := 10 * rev + num mod 10;
if (num = rev) then exit(true);
num := num div 10;
if (num = rev) then exit(true);
end;
Result := false;
end;
А для бинарных чисел добавляем функцию IsBinaryPalindrome в новую
программу:
uses MathExtensions;
function IsBinaryPalindrome(n: int): bool;
begin
// конвертируем число в двоичную строку без ведущих нулей:
var binary := '';
var temp := n;
while temp > 0 do begin
binary := (temp mod 2).ToString + binary;
temp := temp div 2;
end;
// проверяем, является ли двоичная строка палиндромом:
var len := binary.Length;
for var i := 1 to len div 2 do
if binary[i] <> binary[len - i + 1] then
exit(false);
Result := true;
end;
219
В процедуре Solve перебираем все числа в заданном диапазоне:
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
var limit := 1_000_000;
var sum := 0;
var numbersFound := new List<int>;
// проверяем все числа от 1 до limit-1
for var n := 1 to limit - 1 do begin
Если очередное число показало себя в двух палиндромных ипостасях: десятичной и двоичной, то мы добавляем его к результирующей сумме и запоминаем в списке:
if n.IsPalindrome and IsBinaryPalindrome(n) then begin
sum += n;
numbersFound.Add(n);
end;
end;
Все числа окончательно посчитаны:
Writeln($' Найдено чисел: {numbersFound.Count}');
Writeln;
А вот и сами числа:
// печатаем все найденные числа:
foreach var n in numbersFound do begin
// двоичное представление:
var binary := '';
var temp := n;
while temp > 0 do begin
binary := (temp mod 2).ToString + binary;
temp := temp div 2;
220
end;
Writeln($' {n,6} (десятичное) = {binary.PadLeft(20)} (двоичное)');
end;
Writeln;
Writeln($' Сумма всех чисел = {sum}');
// 872187
end;
Наглядно решение задачи выглядит так:
Project Euler. Problem 36
Найдено чисел: 19
1
3
5
7
9
33
99
313
585
717
7447
9009
15351
32223
39993
53235
53835
73737
585585
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
(десятичное)
=
1
=
11
=
101
=
111
=
1001
=
100001
=
1100011
=
100111001
=
1001001001
=
1011001101
=
1110100010111
=
10001100110001
=
11101111110111
=
111110111011111
=
1001110000111001
=
1100111111110011
=
1101001001001011
=
10010000000001001
= 10001110111101110001
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
(двоичное)
Сумма всех чисел = 872187
221
Проект Эйлер037
Исходный код программы находится в файле Эйлер037.pas.
Задача 37 (Problem 37) называется Truncatable Primes.
Усекаемые простые числа
Число 3797 обладает интересным свойством. Будучи само по себе простым
числом, из него можно последовательно выбрасывать цифры слева направо,
число же при этом остается простым на каждом этапе: 3797, 797, 97, 7. Точно
таким же способом можно выбрасывать цифры справа налево: 3797, 379, 37, 3.
Найдите сумму единственных одиннадцати простых чисел, из которых можно
выбрасывать цифры как справа налево, так и слева направо, но числа при этом
остаются простыми.
ПРИМЕЧАНИЕ: числа 2, 3, 5 и 7 таковыми не считаются.
Простое число называется усекаемым, если:
При удалении цифр справа налево все полученные числа простые
При удалении цифр слева направо все полученные числа простые
Например, число 3797 усекаемое:
Правое усечение: 3797 → 379 → 37 → 3 (все простые)
Левое усечение: 3797 → 797 → 97 → 7 (все простые)
222
Вот все 11 усекаемых простых чисел:
23, 37, 53, 73, 313, 317, 373, 797, 3137, 3797, 739397
Глядя или не глядя на них, мы должны прийти к таким выводам:
Только цифры 2, 3, 5, 7 могут начинать и заканчивать искомые числа, так
как однозначные версии должны быть простыми.
Также допускаются нечётные цифры (без пятёрки): 1, 3, 7, 9.
Цифры 0, 4, 6, 8 исключаем.
Прежде чем начать решение задачи в процедуре Solve, пишем функцию
IsTruncatablePrime для проверки числа на простоту и усекаемость:
begin
Writeln(' Project Euler. Problem 37');
Solve;
end.
Тысячу пардонов за каламбур, но проще всего проверить число на простоту:
uses MathExtensions;
// ПРОВЕРЯЕМ ЗАДАННОЕ ЧИСЛО НА УСЕКАЕМОСТЬ
function IsTruncatablePrime(n: integer): boolean;
begin
// число не простое:
if not (n.IsPrime) then exit(false);
Туда же вслед отправляем однозначные числа:
var s := n.ToString;
var len := s.Length;
223
// однозначное число:
if len < 2 then exit(false);
Проверка числа на усекаемость тоже несложна,
если усечь принципы проверки чисел туда и обратно:
// усекаем число справа налево
// (удаляем последнюю цифру):
var temp := n;
while temp > 9 do begin
temp := temp div 10;
if not (temp.IsPrime) then exit(false);
end;
// усекаем число слева направо
// (удаляем первую цифру)
temp := n;
while temp > 9 do begin
var power := 1;
while power * 10 <= temp do
power *= 10;
temp := temp mod power;
if not (temp.IsPrime) then exit(false);
end;
Result := true;
end;
А вот теперь вплотную можно заняться решением задачи!
Самое важное знание для этого – чисел всего 11, поэтому нам не нужно задумываться о диапазоне поиска чисел.
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
var count := 0;
var sum := 0;
var numbersFound := new List<integer>;
224
// первые 4 простых числа пропускаем:
var n := 11;
// всего существует 11 таких чисел:
while count < 11 do begin
// нашли:
if IsTruncatablePrime(n) then begin
count += 1;
sum += n;
numbersFound.Add(n);
Writeln;
Writeln($' Нашли усекаемое простое число {count,2}: {n}');
// процесс усечения найденного числа&
Write(' Усечение справа: ');
var temp := n;
while temp > 0 do begin
Write(temp);
temp := temp div 10;
if temp > 0 then Write(' → ');
end;
Writeln;
Write(' Усечение слева: ');
temp := n;
while temp > 0 do begin
Write(temp);
var power := 1;
while power * 10 <= temp do
power *= 10;
temp := temp mod power;
if temp > 0 then Write(' → ');
end;
Writeln;
end;
// считаем найдёнышей:
n += 1;
end;
Writeln;
Writeln(' Всего найдено усекаемых простых чисел: ', count);
Writeln;
Write(' Числа: ');
foreach var num in numbersFound do
Write(num, ' ');
Writeln;
225
Writeln(' Сумма всех усекаемых простых чисел =', sum);
// 748317
end;
С помощью функции IsTruncatablePrime мы без заботы и труда выявим на
свет эти числа. А сэкономленные силы и энергию направим на красивый
вывод решения на экран.
И вот он, исчерпывающий ответ на задачу:
Project Euler. Problem 37
Нашли усекаемое простое число
Усечение справа: 23 → 2
Усечение слева: 23 → 3
1: 23
Нашли усекаемое простое число
Усечение справа: 37 → 3
Усечение слева: 37 → 7
2: 37
Нашли усекаемое простое число
Усечение справа: 53 → 5
Усечение слева: 53 → 3
3: 53
Нашли усекаемое простое число
Усечение справа: 73 → 7
Усечение слева: 73 → 3
4: 73
Нашли усекаемое простое число 5: 313
Усечение справа: 313 → 31 → 3
Усечение слева: 313 → 13 → 3
Нашли усекаемое простое число 6: 317
Усечение справа: 317 → 31 → 3
Усечение слева: 317 → 17 → 7
Нашли усекаемое простое число 7: 373
Усечение справа: 373 → 37 → 3
Усечение слева: 373 → 73 → 3
226
Нашли усекаемое простое число 8: 797
Усечение справа: 797 → 79 → 7
Усечение слева: 797 → 97 → 7
Нашли усекаемое простое число 9: 3137
Усечение справа: 3137 → 313 → 31 → 3
Усечение слева: 3137 → 137 → 37 → 7
Нашли усекаемое простое число 10: 3797
Усечение справа: 3797 → 379 → 37 → 3
Усечение слева: 3797 → 797 → 97 → 7
Нашли усекаемое простое число 11: 739397
Усечение справа: 739397 → 73939 → 7393 → 739 → 73 → 7
Усечение слева: 739397 → 39397 → 9397 → 397 → 97 → 7
Всего найдено усекаемых простых чисел: 11
Числа: 23 37 53 73 313 317 373 797 3137 3797 739397
Сумма всех усекаемых простых чисел =748317
Проект Эйлер038
Исходный код программы находится в файле Эйлер038.pas.
Задача номер 38 (Problem 38) называется Pandigital multiples.
Пандигитальные сомножители
Возьмем число 192 и умножим его по очереди на 1, 2 и 3:
192 × 1 = 192
192 × 2 = 384
192 × 3 = 576
227
Объединяя все три произведения, получим девятизначное число 192384576
из цифр от 1 до 9 (пан-цифровое число). Будем называть число 192384576
объединенным произведением 192 и (1,2,3)
Таким же образом можно начать с числа 9 и по очереди умножать его на 1,
2, 3, 4 и 5, что в итоге дает пан-цифровое число 918273645, являющееся
объединенным произведением 9 и (1,2,3,4,5).
Какое самое большое девятизначное пан-цифровое число можно образовать как объединенное произведение целого числа и (1,2, ... , n), где n > 1?
Продолжаем бить в одну точку и долбить вдрызг пандигитальные числа.
На этот раз – в задаче 38 из Проекта Эйлера, которая называется Pandigital
multiples, или Пандигитальные сомножители. Её решили менее 70 тысяч
участников проекта, так что эта задача не всем по зубам.
Задача 38 сильно напоминает и смахивает на задачу 32 Пандигитальные
произведения, что нам, конечно, на руку и по плечу. Тем не менее внимательно читаем и вчитываемся в условие задачи, чтобы наметить пути стремительного наступления на неё по всему фронту.
Легко убедиться, что первым сомножителем может быть число 1-9999. А
второй сомножитель изменяется в диапазоне 1..9:
Отмечаем также для себя мотанием на ус, что произведения нужно записывать в одну строку, и в ней должны быть все цифры по одному разу – за исключением нуля. Для проверки строки на пандигитальность пишем функцию IsPandigital, а дальше всё пойдёт как российский сыр по сливочному
маслу!
uses MathExtensions;
// СТРОКА СОСТОИТ ИЗ РАЗНЫХ ЦИФР?
function IsPandigital(s : string) : bool;
begin
Result := (s.Length = 9) and
(s.All(c -> c <> '0')) and
(s.Distinct().Count() = 9);
228
end;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
Для выдачи ответа на задачу запоминаем наибольшее число в переменной
maxnum:
// наибольшее число:
var maxnum: int64 := 0;
В цикле for перебираем все возможные первые сомножители:
// решаем задачу -->
// перебираем первые сомножители:
for var i := 1 to 9999 do begin
// строка для сложения произведений:
var str := String.Empty;
Вторые сомножители перебираем в цикле while, начиная с единицы. По
условию задачи, для каждого первого сомножителя находим произведения
со вторыми сомножителями и добавляем их в строку. Если очередное произведение слишком удлиняет строку, значит, её пора проверять:
// второй сомножитель:
var n := 1;
// последовательно складываем произведения, пока
// в строке меньше девяти цифр:
while (str.Length < 9) do begin
str += i * n;
n += 1;
end;
229
Если проверка строки в функции IsPandigital прошла успешно, то конвертируем строку в число. Самый простой способ для этого – воспользоваться
методом ToInt64 класса Convert:
// нашли пандигитальное число:
if IsPandigital(str) then begin
var num := Convert.ToInt64(str);
Если новое произведение больше текущего, то отмечаем его запоминанием
в переменной maxnum:
// запоминаем, если оно больше текущего значения:
if (num > maxnum) then
maxnum := num;
По ходу дела и ради интереса печатаем все текущие рекорды:
// печатаем промежуточные результаты:
Println($' {i,4}, {n-1} → {str}');
end;
end;
Перебрав все возможные произведения, отчитываемся перед собой полным
отчётом на экране:
// печатаем ответ:
Println($' Наибольшее число = {maxnum}');
WriteLn;
end;
begin
Writeln(' Project Euler. Problem 38');
WriteLn;
Solve();
230
end.
Однажды покумекав, мы можем докумекаться, что можно сравнивать
строки, а не числа, поэтому совсем необязательно конвертировать длинную строку в число:
procedure Solve2();
begin
// наибольшее число:
var maxstr := String.Empty;
// решаем задачу -->
// перебираем первые сомножители:
for var i := 1 to 9999 do begin
// строка для сложения произведений:
var str := String.Empty;
// второй сомножитель:
var n := 1;
// последовательно складываем произведения, пока
// в строке меньше девяти цифр:
while (str.Length < 9) do begin
str += i * n;
n += 1;
end;
// нашли пандигитальное число:
if IsPandigital(str) then begin
if (str.CompareTo(maxstr) > 0) then
maxstr := str;
// печатаем промежуточные результаты:
Println($' {i,4}, {n-1} → {str}');
end;
end;
// печатаем ответ:
Println($' Наибольшее число = {maxstr}');
WriteLn;
end;
В коде кода выпускаем нашу программу на борьбу с иностранными и странными пандигитальными числами.
231
И как мы воочию видим, чтобы убедиться: гораздо выгоднее иметь дело с
большими числами, чтобы преумножать их ещё пуще для своей личной и
наличной выгоды.
232
Проект Эйлер043
Исходный код программы находится в файле Эйлер043.pas.
Задача номер 43 (Problem 43) называется Sub-string Divisibility.
Делимость подстрок
Число 1406357289, является пан-цифровым, поскольку оно состоит из цифр
от 0 до 9 в определенном порядке. Помимо этого, оно также обладает интересным свойством делимости подстрок.
Пусть d1 будет 1-й цифрой, d2 будет 2-й цифрой, и так далее. В таком случае,
можно заметить следующее:
• d2d3d4=406 делится на 2 без остатка
• d3d4d5=063 делится на 3 без остатка
• d4d5d6=635 делится на 5 без остатка
• d5d6d7=357 делится на 7 без остатка
• d6d7d8=572 делится на 11 без остатка
• d7d8d9=728 делится на 13 без остатка
• d8d9d10=289 делится на 17 без остатка
Найдите сумму всех пандигитальных чисел из цифр от 0 до 9, обладающих данным свойством.
Тема пандигитальных чисел ещё далеко не исчерпана и не вычерпана до
дна. И сегодня мы снова глубоко зачерпнём новые пандигитальные числа
из задачи 43 Проекта Эйлера, которая называется Sub-string Divisibility,
или Делимость подстрок. Её решили ещё меньше участников проекта, чем
предыдущие. И это не случайно!
При внимательном и усердном чтении задачи мы можем извлечь важные
для её решения упрямые факты.
Нам нужны для препарирования и последующего исследования все пандигитальные числа, состоящие из десяти цифр.
233
Из этих чисел мы должны последовательно извлекать все трёхзначные
числа, исключая при этом первую цифру пандигитального числа, и проверять их опять же последовательно на простые делители 2,3,5,7,11,13,17.
Эти операции проще и естественнее выполнять со строками, поэтому мы
будем сразу генерировать строки из пандигитальных чисел.
Но, чтобы найти сумму всех затребованных чисел, нам нужны сами числа,
поэтому придётся строки конвертировать в числа, что не очень сложно.
Всё это мы плотно мотаем себе на оба уса, потому что сами с усами. И пора
крутить усы – они у нас не для красы!
Генератор перестановок – это комбинаторная причуда, которую мы возьмём из паскаля в готовом виде. В этой задаче мы не должны дополнительно
обременять себя изобретением комбинаторных велосипедов.
Генератор перестановок получает строку с полным набором цифр и переставляет их как ему заблагорассудится и душе его угодно, а мы получим все
перестановки цифр в виде чисел-строк.
Получив очередную пандигитальную строку, мы отправляем её на проверку в функцию Uslovie. В этой функции мы последовательно извлекаем
подстроки из тройки цифр, начиная со второй (её индекс равен единице),
составляем из них трёхзначное число и делим его на соответствующее простое число из массива primeDivisors. И только если все тройки чисел
успешно и послушно разделятся на свои делители, функция вернёт true. А
иначе нам удачи не видать, а видать только false.
Пригодную для дела строку превращаем в число и добавляем в переменную
res.
Теперь мы можем распечатать все пандигитальные числа, которые не отторгаются условием задачи.
А также легко вычислить сумму этих чисел, чтобы отчеканить наш ответ на
пандигитальный челлендж!
234
Если вы хотите яснее уяснить себе, что такое челлендж, то посмотрите номер Солдатский челлендж из шоу Уральских пельменей
Мятый элемент.
И тут уж делать нечего, кроме как натравить и науськать нашу программу
на пандигитальные числа!
uses MathExtensions;
// ПРОВЕРЯЕМ УСЛОВИЕ ЗАДАЧИ
function Uslovie(num: string): bool;
begin
// простые делители:
var primeDivisors := |2, 3, 5, 7, 11, 13, 17|;
// проверяем для всех делителей:
for var i := 0 to primeDivisors.Length - 1 do begin
var snum3 := num.Slice(i + 1, 1, 3);
var num3 := snum3.ToInteger;
// не делится на очередное простое число:
if num3 mod primeDivisors[i] <> 0 then
exit(false);
235
end;
// число выдержало все проверки:
Result := true;
end;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// решаем задачу -->
var res := '0123456789'.Permutations
// генерируем перестановки
.Where(Uslovie)
// отбираем строки, удовлетворяющие условию
.Select(int64.Parse); // конвертируем строку в
число
// печатаем все числа,
// для которых выполняется условие задачи:
foreach var n in res do
Println($' Число = {n}');
// находим сумму этих чисел:
var sum := res.Sum();
// печатаем ответ:
Println($' Сумма = {sum}');
WriteLn;
end;
begin
Writeln(' Project Euler. Problem 43');
WriteLn;
Solve();
end.
И она бодро и хватко наловила полный улов пандигитальных чисел и
быстро сложила их в сумку и в сумму. Пандигитальный трофей оказался не
маленьким, а вот такенным здоровенным!
Project Euler. Problem 43
236
Число
Число
Число
Число
Число
Число
Сумма
=
=
=
=
=
=
=
1406357289
1430952867
1460357289
4106357289
4130952867
4160357289
16695334890
Как наставлял нас в школе на ум Александр Сергеевич Грибоедов в комедии
"Горе от ума", Шумим, братец, шумим.
Из этого следует, что горе от ума порождает ненужный и даже вредный
шум. Это особенно верно для убытка здоровья, если речь заходит о техническом и научном уме, от которого и много шума, и много горя.
237
Проект Эйлер041
Исходный код программы находится в файле Эйлер041.pas.
Задача номер 41 (Problem 41) называется Pandigital Prime.
Пандигитальное простое число
Будем считать n-значное число пан-цифровым, если каждая из цифр от 1 до
n используется в нем ровно один раз. К примеру, 2143 является 4-значным
пан-цифровым числом, а также простым числом.
Какое существует наибольшее n-значное пан-цифровое простое число?
Вы будете смеяться до упаду, но и сегодня мы будем решать проект-эйлеровскую задачу номер 41 про пандигитальные числа.
Как следует из условия задачи, Эйлер предлагает нам отыскать наибольшее простое пандигитальное число.
Однозначное число 1 пандигитальное, но не простое. Легко проверить, что
на тройку не делятся только четырёх- и семизначные пандигитальные
числа (вспомните, если сможете, признак делимости на 3). Все остальные
пандигитальные числа не простые, а составные как раз по этой причине.
Вполне разумно и логично начать поиски с семизначных чисел. А уж ежели
не повезёт, то вернуться вспять к четырёхзначным, среди которых безусловно есть хотя бы одно подходящее.
Генератор перестановок для строк у нас уже имеется в полном достатке и
в готовом виде. Мы опробовали его в боевой обстановке, и он показал себя
с лучшей стороны и вёл себя героически.
Также в нашей математической копилке есть метод расширения IsPrime
для проверки чисел на простоту. Да с такой подготовкой мы осилим любого
проектанта Эйлера. Кстати говоря, эту задачу решили более 75 тысяч
238
человек, так что это не самая большая загвоздка для математического гвоздодёра.
С генератором перестановок вы уже близко познакомились в предыдущих
проектах, посему идём дальше.
В строку digits записываем всю премудрую цифирь, необходимую для производства семизначных пандигитальных чисел:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// цифры:
var digits := '7654321';
//var digits = '4321';
Важная переменная maxPrime хранит наибольшее пандигитальное простое число. Нуль служит сигналом, что мы не нашли нужного числа.
// наибольшее число:
var maxPrime: int64 := 0;
В цикле foreach мы последовательно получаем все строковые перестановки цифр 1-7.
// решаем задачу:
foreach var perm in digits.Permutations do begin
Конвертируем строку в число:
// конвертируем строку в число:
var num := perm.ToInteger;
239
Нас по долгу службы интересуют только числа, которые больше текущего
значения переменной maxPrime, но обязательно простые:
// проверяем условие задачи:
if (num > maxPrime) and (num.IsPrime) then
maxPrime := num;
end;
Закончив и покончив со всеми семизначными пандигитальными простыми
числами, мы печатаем самое крупное из них для отчёта перед собой и на
экране:
// печатаем ответ:
if (maxPrime = 0) then
Println($' Решений нет!')
else
Println($' Наибольшее пандигитальное простое число = {maxPrime}');
writeln;
end;
begin
Writeln(' Project Euler. Problem 41');
WriteLn;
Solve();
end.
Запускаем программу в математическое пространство.
После успешного выхода программы на пандигитальную орбиту она сообщает нам свысока ответ на эйлеровскую задачу. Ответ верный, математикой клянусь!
Project Euler. Problem 41
Наибольшее пандигитальное простое число = 7652413
240
Так как ноль не изменяет сумму цифр числа, то мы можем поискать пандигитальное число с нулём внутри себя:
Project Euler. Problem 41
Наибольшее пандигитальное простое число = 76540231
Учимся вдогонку смехотворно расширять свой словарный запас наружу.
Проще всего глумиться над словарным запасом, просто подменяя одну или
несколько буквой другими так, чтобы придать слову новое, шуточное значение.
Например, к примеру всем известно слово домочадцы. Это устаревшее
название членов семьи, которые толпятся и толкутся без дела в одном доме.
Но давайте заменим букву О буквой Ы, чтобы получить слово с новым смыслом – дЫмочадцы. Это уже такие членовредительные члены семьи, которые
чадят дымом, когда женщины занимаются домашним хозяйством.
241
Проект Эйлер104
Исходный код программы находится в файле Эйлер104.pas.
Задача номер 104 (Problem 104) называется Pandigital Fibonacci Ends.
Пандигитальные концы чисел Фибоначчи
Последовательность Фибоначчи определяется следующим рекуррентным
выражением:
Fn = Fn−1 + Fn−2, где F1 = 1 и F2 = 1.
Оказывается, что число F541, состоящее из 113 цифр, является первым числом Фибоначчи, у которого последние девять цифр образуют пандигитальное число с цифрами от 1 до 9 (оно содержит все цифры от 1 до 9, но не обязательно в порядке возрастания). А число F2749, состоящее из 575 цифр, является первым числом Фибоначчи, у которого первые девять цифр образуют
пандигитальное число с цифрами от 1 до 9.
Известно, что число Fk является первым числом Фибоначчи, у которого как
первые, так и последние девять цифр образуют пандигиатльные числа.
Найдите k.
Ретивые математики-селекционеры скрестили пандигитальные числа с
простыми. Произошло это в задаче номер 41 из Проекта Эйлера. Легко догадаться и смекнуть, что на этом они не остановились и не успокоили свои
трудовые и научные порывы к скрещиванию чисел.
Нет, они бодрым шагом пошли дальше вперёд, чтобы очаровать нас новой
гибридохимерой – пандигитальными числами Фибоначчи уже в задаче номер 104.
Тут следует отметить, что уже из условия задачи ясно и однозначно следует, что в природе нет по-настоящему цельных, без разрывов и надрывов
пандигитальных чисел Фибоначчи.
242
Однако есть числа Фибоначчи, которые начинаются или заканчиваются на пандигитальные
числа с цифрами 1..9. Они ну очень большие –
как раки, которые были вчера по 5 рублей.
Совершенно очевидно, что число Фибоначчи,
которое имеет пандигитальные голову и хвост,
ещё того больше. И даже гораздо больше. Но для нас важно, что такое число
есть и существует. Его-то мы и должны найти и отыскать, как это уже
успешно сделали более 18 тысяч человек.
Избегая напрочь хитрых математических приёмов, мы отыщем ответ простым перебором среди огромных чисел типа BigInteger. Это потребует от
нас некоторого терпения и прочего терпежа. Но оно того стóит.
Пишем новую функцию IsPandigital для проверки чисел на пандигитальность:
uses MathExtensions;
// ПРОВЕРЯЕМ ЧИСЛО НА ПАНДИГИТАЛЬНОСТЬ
function IsPandigital(n: int64): bool;
begin
var s := n.ToString();
Result := s.OrderBy(c -> c).SequenceEqual('123456789');
end;
Первые два числа Фибоначчи сразу и всем известны. Все последующие мы
генерируем на лету́ в бесконечном цикле while, в котором также считаем
номера чисел Фибоначчи, чтобы дать потом наш достойный ответ господину Эйлеру:
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// первые числа Фибоначчи:
var f1: BigInteger := 1;
var f2: BigInteger := 1;
243
// номер текущего числа Фибоначчи:
var k := 2;
// решаем задачу:
while true do begin
k += 1;
// текущее число Фибоначчи:
var current := f1 + f2;
(f2, f1) := (f1, current);
Последние 9 цифр текущего числа Фибоначчи легко найти как результат
от деления по модулю на число с девятью нулями.
// проверяем последние 9 цифр:
var last9 := current mod 1_000_000_000;
Весьма желательно, чтобы хвост оказался пандигитальным.
if not IsPandigital(int64(last9)) then
continue;
Оторвать голову числу Фибоначчи сложнее, поэтому мы здесь слегка схитрим и обманем себя, превратив число в строку. От строки мы отрежем первые 9 символов, которые снова превратим в число, чтобы проверить его на
пандигитальность:
var fs9 := fs.Substring(0, 9);
var first9 := int.Parse(fs9);
if not IsPandigital(first9) then
continue;
Не рано, но и не поздно мы получим номер числа Фибоначчи, которое как
раз вовсю годится под условие задачи. Тут нам ура, чепчики и победоносный ответ на экране:
244
// печатаем ответ:
Println($' k = {k}');
writeln;
break;
end;
end;
begin
Writeln(' Project Euler. Problem 104');
WriteLn;
Solve();
end.
Запускаем программу.
После слегка томительного и утомительного ожидания мы получим совсем
немаленький номер числа Фибоначчи, который пандигитальный с обоих
концов. А вот кто придумал эту задачу, мы так и не узнали.
Project Euler. Problem 104
k = 329468
245
Чтобы подняться над собой в чине и стать ближе к деньгам, нужно корячиться наверх, преодолевая препятствия. Но в отличие от альпинистов, карьеристы прокладывают себе путь наверх не только с помощью ног и рук,
но – и преимущественно – угодливого языка, которым следует владеть в совершенстве, иначе никак невозможно подняться по скользкой и опасной карьерной ЛЕСТЬНИЦЕ.
246
Проект Эйлер142
Исходный код программы находится в файле Эйлер142.pas.
Задача номер 142 (Problem 142) называется Perfect Square Collection.
Find the smallest x + y + z with integers (x > y > z > 0) such that x + y, x – y, x + z, x –
z, y + z, y – z are all perfect squares.
Коллекция полных квадратов
Найдите тройку натуральных чисел x, y и z таких, что выражения x + y,
x - y, x + z, x - z, y + z, y – z представляют собой квадраты натуральных чисел.
При этом сумма чисел x + y + z должна быть минимальной.
Соблазнительно решить задачу методом грубой силы, просто перебирая
числа в трёх вложенных циклах for. Имея много времени, можно дождаться
решения задачи, но через пару минут становится очевидно, что на сайте
Проект Эйлера не предложили бы задачу на банальный перебор в циклах.
Значит, проблему нужно решать иначе.
Поскольку все выражения – это квадраты натуральных чисел, то мы можем
обозначить их так:
x
x
x
x
y
y
–
+
–
+
–
+
y
y
z
z
z
z
=
=
=
=
=
=
a2
b2
c2
d2
e2
f2
(1)
(2)
(3)
(4)
(5)
(6)
Складываем два первых равенства:
x – y + x + y = a2 + b2
247
Откуда:
x = (a2 + b2)/2
(7)
Вычитая из второго равенства первое, получаем:
y = (b2 - a2)/2
(8)
Из равенств (7) и (8) вытекает, что числа a и b либо оба чётные, либо оба
нечётные, иначе числа x и y будут дробными.
Так как y > 1, то
(b2 - a2)/2 > 1
и
b > a
Вычитая из выражения (4) выражение (3), получаем:
x + z – (x - z) = d2 + c2
Или:
z = (d2 - c2)/2
(9)
Складывая выражения (3) и (4), находим:
x + z + (x - z) = d2 + c2
x = (d2 + c2)/2
Из выражения (7) следует, что:
x = (d2 + c2)/2
= (a2 + b2)/2
То есть:
d2 = a2 + b2 - c2
Подставляя в выражение (9), получаем формулу для вычисления z:
248
z = (a2 + b2 - c2 - c2)/2
z = (a2 + b2)/2 - c2
z = x - c2
Нам опять потребуются 3 вложенных цикла для чисел a, b и c, но теперь
диапазон их изменения гораздо меньше!
Поскольку b > a > 1, то b не может равняться 1 и 2, в противном случае а
будет равно 0. Итак, переменная цикла b изменяется от 3 до – пока мы не
найдём решения задачи. Переменная цикла а изменяется от 1 или от 2 – в
зависимости от того, нечётное или чётное число b. Причём а изменяется с
приращением 2, иначе его чётность будет отличаться от чётности числа b.
Для числа с имеем:
z < y → x - c2 < y → x - y < c2 → (a2 + b2)/2 - (b2 - a2)/2 < c2 →
a2 < c2 → a < c
С другой стороны,
z = x – c2 > 0 → x > c2 → c < √𝑥
Так как
x = (a2 + b2)/2
и a < b, то с < b.
Таким образом, переменная цикла c изменяется от a + 1 до b - 1.
После теоретических изысканий мы без труда напишем процедуру для решения задачи:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
249
begin
var b := 3;
while True do begin
var b2 := b * b;
for var a := 1 + (0 and b) to b - 1 step 2 do begin
var a2 := a * a;
var x := (a2 + b2) shr 1;// 2;
for var c := a + 1 to b - 1 do begin
var y := (b2 - a2) shr 1;// 2;
var z := x - c * c;
if IsQuadrat(y IsQuadrat(y +
IsQuadrat(x IsQuadrat(x +
begin
| a, b, c,
WriteLn($'
Writeln;
WriteLn($'
WriteLn($'
WriteLn($'
WriteLn($'
WriteLn($'
WriteLn($'
Writeln;
exit;
end;
end;
end;
b += 1;
end;
Writeln(' РЕШЕНИЙ НЕТ');
Writeln;
end;
z)
z)
z)
z)
and
and
and
then
x, y, z |.Println;
Наименьшая сумма x+y+z равна {x+y+z}');
x-y
x+y
x-z
x+z
y-z
y+z
=
=
=
=
=
=
{x-y}
{x+y}
{x-z}
{x+z}
{y-z}
{y+z}
-> {Sqrt(x-y)} * {Sqrt(x-y)}');
-> {Sqrt(x+y)} * {Sqrt(x+y)}');
-> {Sqrt(x-z)} * {Sqrt(x-z)}');
-> {Sqrt(x+z)} * {Sqrt(x+z)}');
-> {Sqrt(y-z)} * {Sqrt(y-z)}');
-> {Sqrt(y+z)} * {Sqrt(y+z)}');
begin
Writeln(' Project Euler. Problem 142');
Writeln;
Solve();
end.
Здесь мы применили некоторую оптимизацию кода, чтобы ускорить процесс.
250
Условный оператор:
if (b mod 2 = 1) then a := 1
else a := 2;
заменили побитовым:
a := 1 + (0 and b)
Деление на двойку – оператором сдвига вправо.
Задача оказалась непростой!
Project Euler. Problem 142
117 925 533 434657 420968 150568
Наименьшая сумма x+y+z равна 1006193
x-y
x+y
x-z
x+z
y-z
y+z
=
=
=
=
=
=
13689
855625
284089
585225
270400
571536
->
->
->
->
->
->
117
925
533
765
520
756
*
*
*
*
*
*
117
925
533
765
520
756
Проект Пандигитальные квадраты
Исходный код программы находится в файле Пандигитальные квадраты.pas.
Мы ощутимо пощупали с разных сторон и боков пандигитальные числа в
эйлеровских проектах, но вдобавок и вдогонку к ним у нас созрели пандигитальные квадраты.
251
Эта задача не новая. Она имеет давнюю историю. Например, в журнале
Наука и жизнь, №11 за 1970 год я отыскал сообщение, что найдены 85 10значных пандигитальных квадратов.
А ещё раньше, в номере 7 нашлось и начало этой задачи. Там были показаны
лицом народу два 10-значных пандигитальных квадрата. Итого в 1970 году
читатели журнала Наука и жизнь нашли все 87 10-значных пандигитальных квадратов.
Как они это сделали мы не знаем, но персональных компьютеров и программируемых калькуляторов в 1970 году ещё не было. А у нас компьютеры есть
252
и в наличии, так что мы не сломя голову должны повергнуть и подмять под
себя эту задачу из прошлого века.
Но, чтобы не просто повторить этот героический подвиг, мы найдём ещё и
9-значные пандигитальные квадраты. Тут мы можем действовать по аналогии с 10-значными пандигитальными квадратами, так что в общем и целом работы нам предстоит немного, можно даже не засучать спустя рукава.
253
Для легкоты́ и легкови́ зны восприятия решения задачи мы сначала отыщем
девятизначные пандигитальные квадраты, а вслед за ними и вдогонку – десятизначные.
Подробно рассмотрим отыск девятизначных пандигитальных квадратов,
а с десятизначными поступайте так же, но строже.
Вычисляем пределы изменения чисел, которые порождают девятизначные
квадраты. Это сугубая математика.
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
Println($' Квадратные пандигитальные числа (цифры 1..9, без нуля):');
var minN := ISqrt(123456789); // ~11112
var maxN := ISqrt(987654321); // ~31426
Для всезнающей статистики считаем пандигитальные квадраты, которые
мы ищем в цикле for - от и до - как мы это порешили только что раньше.
var count19 := 0;
// решаем задачу:
for var n := minN to maxN do begin
Вычисляем квадрат текущего значения переменной цикла.
var square: int64 := n * n;
Крайне желательно, чтобы квадрат оказался пандигитальным. Но это уже
решаем не мы, а судьба в образе функции IsPandigital из модуля
MathExtensions.
254
// ПРОВЕРЯЕМ ЧИСЛО С ЦИФРАМИ 1.. 9 НА ПАНДИГИТАЛЬНОСТЬ
function IsPandigital(n: int64): bool;
begin
var s := n.ToString();
Result := s.OrderBy(c -> c)
.SequenceEqual('123456789');
end;
Печатаем очередной отысканный девятизначный пандигитальный квадрат, не забывая преувеличивать и преумножать счётчик квадратных найдёнышей:
if IsPandigital(square) then begin
count19 += 1;
// печатаем число::
Println($' {n}^2 = {square}');
end;
end;
Окончательно отчитываемся перед собой числом найденных девятизначных пандигитальных квадратов.
Println($' Найдено {count19} квадратных пандигитальных чисел (цифры
0..9, без нуля)');
WriteLn;
С десятизначными пандигитальными квадратами действуйте совершенно
аналогично, только длиннее, чем короче:
// ПРОВЕРЯЕМ ЧИСЛО С ЦИФРАМИ 0.. 9 НА ПАНДИГИТАЛЬНОСТЬ
function IsPandigital0To9(n: int64): bool;
begin
// в числе должно быть 10 цифр 0..9:
var s := n.ToString();
Result := s.OrderBy(c -> c)
.SequenceEqual('0123456789');
255
end;
Println($' Квадратные пандигитальные числа (цифры 0..9, с нулём):');
// диапазон n, при котором n^2 — 10-значное число:
minN := ISqrt(1023456789); // ~31992
maxN := ISqrt(9876543210); // ~99380
var count09 := 0;
// решаем задачу:
for var n := minN to maxN do begin
var square: int64 := n * n;
if IsPandigital0To9(square) then begin
count09 += 1;
// печатаем число::
Println($' {n}^2 = {square}');
end;
end;
Println($' Найдено {count09} квадратных пандигитальных чисел (цифры
0..9, с нулём)');
WriteLn;
end;
begin
Writeln(' Пандигитальные квадраты');
WriteLn;
Solve();
end.
Запускаем программу вза́ пуски. И тут же наблюдаем весьма приятную для
пущих глаз картину с исчерпывающем списком всех полноценных пандигитальных квадратов, коих оказалось: сначала 30, а затем - 87. Всё строго по
науке:
Пандигитальные квадраты
Квадратные пандигитальные числа (цифры 1..9, без нуля):
11826^2 = 139854276
12363^2 = 152843769
19629^2 = 385297641
20316^2 = 412739856
25059^2 = 627953481
25572^2 = 653927184
256
12543^2
14676^2
15681^2
15963^2
18072^2
19023^2
19377^2
19569^2
=
=
=
=
=
=
=
=
157326849
215384976
245893761
254817369
326597184
361874529
375468129
382945761
22887^2
23019^2
23178^2
23439^2
24237^2
24276^2
24441^2
24807^2
=
=
=
=
=
=
=
=
523814769
529874361
537219684
549386721
587432169
589324176
597362481
615387249
25941^2
26409^2
26733^2
27129^2
27273^2
29034^2
29106^2
30384^2
=
=
=
=
=
=
=
=
672935481
697435281
714653289
735982641
743816529
842973156
847159236
923187456
Найдено 30 квадратных пандигитальных чисел (цифры 0..9, без нуля)
Квадратные пандигитальные числа (цифры 0..9, с нулём):
32043^2
32286^2
33144^2
35172^2
35337^2
35757^2
35853^2
37176^2
37905^2
38772^2
39147^2
39336^2
40545^2
42744^2
43902^2
44016^2
45567^2
45624^2
46587^2
48852^2
49314^2
49353^2
50706^2
53976^2
54918^2
55446^2
55524^2
55581^2
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
1026753849
1042385796
1098524736
1237069584
1248703569
1278563049
1285437609
1382054976
1436789025
1503267984
1532487609
1547320896
1643897025
1827049536
1927385604
1937408256
2076351489
2081549376
2170348569
2386517904
2431870596
2435718609
2571098436
2913408576
3015986724
3074258916
3082914576
3089247561
56532^2
57321^2
58413^2
58455^2
58554^2
59403^2
60984^2
61575^2
61866^2
62679^2
62961^2
63051^2
63129^2
65634^2
65637^2
66105^2
66276^2
67677^2
68763^2
68781^2
69513^2
71433^2
72621^2
75759^2
76047^2
76182^2
77346^2
78072^2
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
3195867024
3285697041
3412078569
3416987025
3428570916
3528716409
3719048256
3791480625
3827401956
3928657041
3964087521
3975428601
3985270641
4307821956
4308215769
4369871025
4392508176
4580176329
4728350169
4730825961
4832057169
5102673489
5273809641
5739426081
5783146209
5803697124
5982403716
6095237184
80361^2
80445^2
81222^2
81945^2
83919^2
84648^2
85353^2
85743^2
85803^2
86073^2
87639^2
88623^2
89079^2
89145^2
89355^2
89523^2
90144^2
90153^2
90198^2
91248^2
91605^2
92214^2
94695^2
95154^2
96702^2
97779^2
98055^2
98802^2
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
6457890321
6471398025
6597013284
6714983025
7042398561
7165283904
7285134609
7351862049
7362154809
7408561329
7680594321
7854036129
7935068241
7946831025
7984316025
8014367529
8125940736
8127563409
8135679204
8326197504
8391476025
8503421796
8967143025
9054283716
9351276804
9560732841
9614783025
9761835204
257
55626^2 = 3094251876
78453^2 = 6154873209
99066^2 = 9814072356
Найдено 87 квадратных пандигитальных чисел (цифры 0..9, с нулём)
Программисты люди серьёзные, поэтому любят шутить, играючи шаля словами и коверкая их вкось. Всем известен машинный перевод с одного
языка на другой. И обратно, и опять.
Слегка каламбурим, чтобы получить мышиный перевод, то есть перевод
старых мышек через дорогу молодыми мышками.
Проект Кратные пандигитальные числа
Исходный код программы находится в файле Кратные пандигитальные числа.pas.
258
Кратные пандигитальные числа
Если мы умножим какое-либо пандигитальное число на однозначное число k,
то произведение может оказаться другим пандигитальным числом.
Найдите все пары таких чисел.
Эта задача сродни задаче 032 из Проекта Эйлера.
Пределы изменения числа k легко установить:
• Если k = 0, то произведение всегда равно нулю.
• Если k = 1, то произведение равно исходному числу.
• Если k > 9, то произведение будет 11-значным числом, то есть хотя бы две
цифры обязательно повторятся.
Правильно переставлять цифры мы научились:
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// цифры:
var digits := (0..9);
// множитель:
var k := 9;
// считаем пандигитальные числа:
var np := 0;
// перебираем все перестановки
foreach var perm in digits.Permutations do begin
В этом проекте нам нужна не строка, а число, поэтому пишем новый метод
расширения для последовательностей ToNumber, который возвращает
число типа int64, собранное из заданной последовательности цифр:
function ToNumber(self: sequence of int): int64;
begin
var num: int64 := 0;
foreach var d in self do begin
extensionmethod;
259
if (d < 0) or (d > 9) then
raise new System.ArgumentException('Цифры должны быть в диапазоне 0–9!');
num := num * 10 + d;
end;
Result := num;
end;
Итак, метод Permutations последовательно выдаёт массивы перестановок
десяти цифр, а мы превращаем их в число:
var num := perm.ToNumber;
Умножаем на заданное однозначное число k:
var numk := num * k;
Если произведение не пандигитальное, переставляем цифры дальше:
if not IsPandigital0To9(numk) then continue;
В противном, но благоприятном для нас случае мы увеличиваем счётчик
найденных пандигитальных чисел и печатаем очередную пару на экране:
// увеличиваем счётчик пандигитальных чисел:
np += 1;
Writeln($' {num} * {k} = {num * k}');
end;
Заканчиваем перебор публикованием числа найденных пандигитальных
произведений:
Writeln($' Всего: {np}');
WriteLn;
260
end;
begin
Writeln(' Кратные пандигитальные числа');
WriteLn;
Solve();
end.
261
Пандигитальных произведений оказалось очень много!
519327486 * 2
519328647 * 2
519328746 * 2
519346287 * 2
Всего: 211968
=
=
=
=
1038654972
1038657294
1038657492
1038692574
1643928507 *
1643928570 *
1645270839 *
1645839027 *
Всего: 2316
6
6
6
6
=
=
=
=
9863571042
9863571420
9871625034
9875034162
695847321 *
712348965 *
712349856 *
712354986 *
Всего: 7584
3
3
3
3
=
=
=
=
2087541963
2137046895
2137049568
2137064958
1408753629 *
1408957362 *
1409352876 *
1409632875 *
Всего: 1497
7
7
7
7
=
=
=
=
9861275403
9862701534
9865470132
9867430125
458137269 * 4
458173926 * 4
458176239 * 4
458192376 * 4
Всего: 10432
=
=
=
=
1832549076
1832695704
1832704956
1832769504
1230879564 *
1234067895 *
1234506789 *
1234567890 *
Всего: 1534
8
8
8
8
=
=
=
=
9847036512
9872543160
9876054312
9876543120
248761953 * 5
248791356 * 5
248791536 * 5
248793156 * 5
Всего: 87552
=
=
=
=
1243809765
1243956780
1243957680
1243965780
1095672483
1096347825
1096734825
1097368245
Всего: 870
9
9
9
9
=
=
=
=
9861052347
9867130425
9870613425
9876314205
*
*
*
*
Проект Enigma 1436: One more step
Исходный код программы находится в файле Enigma1436.pas.
В журнале New Scientist #2597 от 31-го марта 2007 года была напечатана задача Enigma 1436:
Задача номер 1436 называется One more step.
262
I invite you to consider the following series of numbers: 1, 3, 7, 13, 21 …
Then find the following:
(a) The six-hundredth member of the series.
(b) A member of the series above the first with less than five digits which is a perfect
cube.
(c) A member which is a five-digit palindrome which can also be read as a binary
number.
(d) The smaller of the two consecutive members which are 1000 apart.
Рассмотрим следующий ряд чисел: 1, 3, 7, 13, 21 …
Найдите:
(a) Шестисотый член ряда.
(b) Член ряда, который:
больше первого,
состоит менее чем из пяти цифр и
является кубом целого числа.
(c) Член ряда, который:
является палиндромом с пятью цифрами,
может быть прочитан как двоичное число.
(d) Два наименьших последовательных члена ряда, разность между которыми равняется 1000.
Прежде чем решать задачу, мы должны научиться вычислять члены ряда.
Обозначим ряд буквой a, тогда первый член равняется:
а1 = 1
Обозначим разность между членами ряда буквой d, тогда:
263
d1 = 3-1 = 2
d2 = 7-3 = 4
d3 = 13-7 = 6
d4 = 21-13 = 8
. . .
dn = 2 * n,
где n – номер члена ряда.
Теперь мы легко найдём любой член ряда:
an = an-1 + dn
Для вывода нерекуррентной формулы воспользуемся методом исчисления конечных разностей, который описан в книге Мартина Гарднера [ГМ72],
на страницах 81-95:
1 3 7 13 21
2 4 6 8
2 2 2
Откуда:
an = n2 – n + 1
(1)
Переходим к решению задачи и вызываем процедуру Solve, которая и даст
нам ответы на все вопросы:
begin
Writeln(' Enigma1436');
Writeln;
Solve();
end.
264
Подзадачу (а) мы решаем, просто подставив номер члена 600 в формулу (1):
uses MathExtensions;
// ВЫЧИСЛЯЕМ n-ный ЧЛЕН РЯДА
function GetAn(n: int) := n * n - n + 1;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
//(a)
WriteLn($' n = {600,3}
a = {GetAn(600),3}');
Writeln;
end;
Enigma1436
n = 600
a = 359401
Для решения подзадачи (b) у нас есть простой метод расширения, который
умеет определять, является ли заданное целое число точным кубом:
function IsCube(Self: int64): boolean; extensionmethod;
begin
var r := Trunc(Round(Power(self, 1.0 / 3.0)));
Result := r * r * r = self;
end;
Все условия этой подзадачи легко записать внутри бесконечного цикла
while:
//(b)
var n := 1;
while True do begin
265
var a := GetAn(n);
if (n > 1) and (a <= 9999) and a.IsCube then begin
WriteLn($' n = {n,3} a = {a,3} cube = {Power(a, 1.0 / 3)}');
break;
end;
n += 1;
end;
Здесь:
- Номер n искомого члена ряда больше единицы:
n > 1
- Искомый член a ряда состоит менее чем из пяти цифр:
a <= 9999
- Искомый член ряда a – точный куб:
a .IsCube()
Поскольку мы заранее не знаем, какой член ряда удовлетворит этим условиям, то запускаем бесконечный цикл, который прерываем сразу, как
только найдём заданный член ряда:
Enigma1436
n = 600
n = 19
a = 359401
a = 343 cube = 7
Подзадачу (с) мы решаем аналогично, но условие прекращения цикла
должно включать проверки, что очередной член ряда:
•
•
•
пятизначное число
палиндром
состоит только из цифр 0 и 1
266
//(c)
n := 0;
while True do begin
n += 1;
var a := GetAn(n);
if (a > 99999) then break;
if (a < 10000) then continue;
var s := a.ToString();
var r := s.Reverse;
if (s.Contains('0')) or (s.Contains('1')) and (s = r) then
begin
WriteLn($' n = {n,4} a = {s,4}');
break;
end;
end;
Здесь:
- Искомый член a ряда состоит из пяти цифр:
if (a > 99999) then break;
if (a < 10000) then continue;
- Искомый член ряда a – палиндром:
var s := a.ToString;
var r := s.Reverse;
(s = r)
- Искомый член ряда состоит из цифр 0 и 1:
(s.Contains('0')) or (s.Contains('1'))
Все условия соблюдены, запускаем программу – и находим решение подзадачи (с):
Enigma1436
267
n = 600 a = 359401
n = 19 a = 343 cube = 7
n = 101 a = 10101
И последнюю подзадачу мы решаем без труда:
//(d)
n := 1;
while True do begin
var a0 := GetAn(n);
var a1 := GetAn(n+1);
if a1 - a0 = 1000 then begin
WriteLn($' n0 = {n,3} n1 = {n + 1,3}
break;
end;
n += 1;
end;
a0 = {a0,6}
a1 = {a1,6}');
Если мы начнём с первого члена ряда (n=1), то нам нужно найти разность
между ним и следующим членом ряда, номер которого на единицу больше.
Если эта разность не равняется 1000, то переходим ко второму члену ряда
и находим разность между ним и третьим членом ряда. Продолжаем так до
тех пор, пока не получим заданную разность:
Enigma1436
n = 600 a = 359401
n = 19 a = 343 cube = 7
n = 101 a = 10101
n0 = 500 n1 = 501 a0 = 249501
a1 = 250501
Проект Enigma 1496: Eighteen
Исходный код программы находится в файле Enigma1496.pas.
268
В журнале New Scientist #2658 от 31-го марта 2008 года была напечатана задача Enigma 1496:
Задача номер 1496 называется Eighteen.
An eight-digit “multiplicand” comprising dots and letters is multiplied by 2 resulting
in the product EIGHTEEN, as shown in the multiplication below, where different letters stand for different digits, the same letter stands for the same digit, and a dot can
be any digit.
• • N I N E • •
× 2
--------------E I G H T E E N
What is the eight-letter product EIGHTEEN?
При умножении восьмизначного числа на 2 получается произведение
EIGHTEEN:
• • N I N E • •
× 2
--------------E I G H T E E N
Разным буквам соответствуют разные цифры, и наоборот. Точки заменяют любые цифры.
Найдите восьмибуквенное произведение EIGHTEEN.
Легко подсчитать, что в задаче используются всего 6 разных букв: E, I, G, H,
T и N, которые могут принимать значения от 0 до 9.
• Для решения задачи мы составим все размещения 6 чисел из 10.
• После каждого размещения присвоим значения переменным
e,i,g,h,t,n.
• Вычислим значение произведения eighteen.
269
• Разделим eighteen на 2.
• Вычислим значение переменной nine.
• Выделим из числа eighteen 4 средние цифры и сравним с nine. Если
они совпали, то решение найдено.
Для генерирования всех размещений воспользуемся библиотекой Generics
Combinatorics:
{$reference GenericsCombinatorics.dll}
uses Facet.Combinatorics;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var arr := Range(0, 9).ToArray();
var vars := new Variations<integer>(arr, 6, GenerateOption.WithoutRepetition);
В цикле foreach просматриваем все размещения и присваиваем значения
переменным:
var num := 0;
foreach var v in vars do begin
При этом мы учитываем, что Е не может быть нулём, а N – чётная цифра:
var e
if (e
var n
if (n
var i
var g
var h
var t
:= v[0];
= 0) then continue;
:= v[1];
mod 2 <> 0) then continue;
:= v[2];
:= v[3];
:= v[4];
:= v[5];
Зная значения всех переменных, вычисляем eighteen и nine:
270
var eighteen := ((((((e*10 + i)*10 + g)*10 + h)*10 + t)*10 + e)*10
+ e)*10 + n;
Находим половину числа eighteen, выделяем их него 4 средние цифры и
сравниваем их с числом nine:
var eighteen2 := eighteen div 2;
var nine := ((n * 10 + i) * 10 + n) * 10 + e;
if (eighteen2 div 100) mod 10000 <> nine then continue;
// нашли решение:
Writeln($' NINE = {n}{i}{n}{e}');
Writeln($' EIGHTEEN = {e}{i}{g}{h}{t}{e}{e}{n}');
num += 1;
end;
Writeln($' Всего решений: {num}');
Writeln;
end;
begin
Writeln(' Enigma1496');
Writeln;
Solve();
end.
Запускаем программу и находим единственное решение задачи:
Enigma 1496: Eighteen
NINE = 8189
EIGHTEEN = 91637998
Всего решений: 1
271
Проект Enigma 1504: All ten digits
Исходный код программы находится в файле Enigma1504.pas.
В журнале New Scientist #2666 от 26-го июля 2008 года была напечатана задача Enigma 1504:
Задача номер 1504 называется All ten digits.
Harry has chosen some 5-digit perfect squares (all different) which use only the digits 0 to 4, none of them starting or finishing with the digit 0. Each of the digits 0 to
4 is used a different number of times in his squares, those numbers of times being 5
to 9.
(1) Which eligible square or squares has Harry NOT chosen?
Tom has chosen some 5-digit perfect squares which only use the digits 5 to 9. Each
of the digits 5 to 9 is used a different number of times in his squares, those numbers
of times being 0 to 4.
(2) Which squares has Tom chosen?
По-русски её смысл можно передать такими словами.
Гарри выбрал несколько разных пятизначных квадратов натуральных чисел, которые состоят только из цифр 0..4, но ни одно из них не начинается и
не заканчивается на 0. Каждая из этих цифр в выбранных числах встречается от 5 до 9 раз – для всех цифр суммы различны.
Вопрос 1: Какое число или числа не выбрал Гарри?
Том также выбрал несколько разных пятизначных квадратов натуральных
чисел, которые состоят только из цифр 5..9. Каждая из этих цифр в выбранных числах встречается от 0 до 4 раз – для всех цифр суммы различны.
272
Вопрос 2: Какие числа выбрал Том?
Для ответа на каждый из этих вопросов, прежде всего, нужно найти все пятизначные числа, которые удовлетворяют условиям задачи, а затем выбрать из них нужные.
Наименьшее пятизначное число Гарри – 10000, а наибольшее – 44444, так
что мы легко переберём его числа в цикле:
uses MathExtensions;
var dig1 := | '0', '1', '2', '3', '4' |;
var digits1 := new HashSet<char>(dig1);
var dig2 := | '5', '6', '7', '8', '9' |;
var digits2 := new HashSet<char>(dig2);
// РЕШАЕМ ЗАДАЧУ
procedure Problem1();
begin
var lst := new List<array of char>();
for var i := ISqrt(10000) to ISqrt(44444) do begin
При этом ни одно число не начинается с нуля, а вот последнюю цифру
нужно проверить!
Числа Тома можно найти аналогично:
procedure Problem2();
begin
var lst := new List<array of char>();
for var i := ISqrt(50000) to ISqrt(99999) do begin
И числа Гарри, и числа Тома нужно проверить на наличие в них посторонних «примесей» в виде цифр 5..9 и 0..4, соответственно.
273
Чтобы определить, сколько чисел выбрали Гарри и Том из полученного
списка, найдём суммы вхождений их цифр:
5 + 6 + 7 + 8 + 9 = 35
0 + 1 + 2 + 3 + 4 = 10
Значит, Гарри выбрал 35 : 5 = 7 чисел, а Том выбрал 10:5 = 2 числа.
Таким образом, нужно выбирать из списка такие наборы, чтобы они удовлетворяли условиям задачи.
Поскольку выделять цифры из числа – задача непростая, то лучше перевести их в строку:
var sq := (i * i).ToString();
А затем – в массив цифр:
var csq := sq.ToCharArray();
Создаём из цифр чисел Гарри и Тома множества, а затем с помощью метода
IsSupersetOf класса HashSet легко определяем, содержатся ли все нужные
цифры в массиве цифр csq.
Тогда в списке Гарри окажется 8 чисел, а в списке Тома – 5:
10201
10404
12321
22201
23104
32041
33124
40401
55696
69696
274
97969
98596
99856
Всего подходящих чисел для Гарри – 8. Из них нужно оставить 7, а выбросить одно – оно и является ответом на первую часть задачи.
В двух циклах for мы можем последовательно отобрать возможное лишнее
число и все наборы из 7 чисел:
for var i := 0 to lst.Count-1 do begin
var sum := new int[5];
for var j := 0 to lst.Count-1 do begin
Аналогично мы можем отобрать и 2 числа для Тома:
for var i := 0 to lst.Count-2 do begin
for var j := i + 1 to lst.Count-1 do begin
var sum := new int[10];
Проблема с проверкой сумм может быть решена с помощью логического
массива:
var b := new bool[10];
Начнём с решения первой части задачи.
Отберём в список lst все пятизначные числа, которые не заканчиваются нулём и состоят только из цифр в множестве digits1, то есть 0..4:
// РЕШАЕМ ЗАДАЧУ
procedure Problem1();
begin
var lst := new List<array of char>();
for var i := ISqrt(10000) to ISqrt(44444) do begin
275
var sq := (i * i).ToString();
var csq := sq.ToCharArray();
if (csq[csq.Length-1] = '0') then
continue;
if not digits1.IsSupersetOf(csq) then
continue;
lst.Add(csq);
Writeln(sq);
end;
Если число заканчивается на нуль, то последний символ в массиве csq равняется '0'. Такие числа мы пропускаем.
Если все символы массива csq содержатся в множестве digits1, то оно является надмножеством для множества csq. Если это не так, то метод IsSupersetOf вернёт false (в массиве csq есть хотя бы одна цифра, отсутствующая в
digits1), и мы переходим к следующей итерации.
Если же число выдержало обе проверки, то мы добавляем его в список lst.
Результат отбора чисел вы уже видели выше.
Мы знаем, что одно из восьми чисел – лишнее. В цикле for последовательно
назначаем одно из чисел «лишним». Остальные 7 чисел должны быть такими, чтобы каждая цифра в них присутствовала в разных количествах. Для
этого мы сначала подсчитываем число вхождений каждой цифры в каждое
число и в массиве sum получаем нужные данные:
Writeln;
// проверяем все числа в списке:
for var i := 0 to lst.Count-1 do begin
//WriteLn($' Первое число = {new String(lst[i])}');
// остальные 7 чисел:
var sum := new int[5];
for var j := 0 to lst.Count-1 do begin
if (j = i) then continue;
for var k := 0 to 5-1 do
sum[ord(lst[j][k])-ord('0')] += 1;
276
WriteLn($' Число = {new String(lst[j])}' );
end;
foreach var n in sum do
Write($' {n,1}');
WriteLn();
WriteLn();
Проверяем, что все суммы в массиве sum различны:
// все суммы цифр должны быть различны:
var b := new bool[10];
var flg := true;
foreach var n in sum do
if (b[n]) then begin
flg := false;
break;
end
else b[n] := true;
Если это так, то флаг flg равен true – семёрка чисел выполняет условия задачи, а первое число нужно исключить:
if (flg) then begin
WriteLn($' Исключаем число: {new String(lst[i])}');
exit;
end;
end;
end;
Запускаем программу и получаем ответ на первый вопрос: исключить
нужно число 22201:
Первое
Число
Число
Число
Число
Число
число = 10201
= 10404
= 12321
= 22201
= 23104
= 32041
Первое
Число
Число
Число
Число
Число
число = 12321
= 10201
= 10404
= 22201
= 23104
= 32041
277
Число = 33124
Число = 40401
7 8 8 5 7
Число = 33124
Число = 40401
9 8 7 4 7
Первое число = 10404
Число = 10201
Число = 12321
Число = 22201
Число = 23104
Число = 32041
Число = 33124
Число = 40401
7 9 9 5 5
Первое число = 22201
Число = 10201
Число = 10404
Число = 12321
Число = 23104
Число = 32041
Число = 33124
Число = 40401
8 9 6 5 7
Исключаем число: 22201
Переходим ко второму вопросу задачи.
Начало процедуры Problem2 почти в точности повторяет предыдущую:
procedure Problem2();
begin
var lst := new List<array of char>();
for var i := ISqrt(50000) to ISqrt(99999) do begin
var sq := (i * i).ToString();
var csq := sq.ToCharArray();
if not digits2.IsSupersetOf(csq) then
continue;
lst.Add(csq);
Writeln(sq);
end;
Теперь мы последовательно выбираем по 2 числа и проверяем сумму цифр
для каждой пары чисел. Как только мы найдём такую пару, что все числа в
массиве sum окажутся различными, напечатаем решение на экране и прекратим дальнейшие розыскные работы:
for var i := 0 to lst.Count-2 do begin
for var j := i + 1 to lst.Count-1 do begin
278
var sum := new int[10];
for var k := 0 to 5-1 do
sum[ord(lst[i][k])-ord('0')] += 1;
for var k := 0 to 5-1 do
sum[ord(lst[j][k])-ord('0')] += 1;
// все суммы цифр должны быть различны:
var b := new bool[10];
var flg := true;
for var n := 5 to 9 do
if (b[sum[n]]) then begin
flg := false;
break;
end
else b[sum[n]] := true;
if (flg) then begin
Write(' Искомые числа: ');
Write(new String(lst[i]));
Write(' и: ');
Write(new String(lst[j]));
WriteLn(); WriteLn();
exit;
end;
WriteLn();
end;
WriteLn();
end;
WriteLn();
end;
begin
Writeln(' Enigma 1504: All ten digits');
Writeln;
Problem1();
Problem2();
end.
Искомая пара чисел Тома – 55696 и 97969:
Искомые числа: 55696 и: 97969
279
Для любителей поломать себе голову о функциональное программирование есть другое решение.
Здесь нам сразу понадобится генератор комбинаций:
uses MathExtensions;
// ГЕНЕРИРУЕТ ВСЕ КОМБИНАЦИИ ИЗ k ЭЛЕМЕНТОВ ЗАДАННОЙ КОЛЛЕКЦИИ
function GetCombinations<T>(elements: array of T; k: int): sequence of
array of T;
begin
var n := elements.Length;
if k > n then exit;
// инициализируем индексы:
var indices := ArrGen(k, i -> i);
while true do begin
// создаём комбинацию по текущим индексам:
var combination := new T[k];
for var i := 0 to k - 1 do
combination[i] := elements[indices[i]];
yield combination;
// следующий набор индексов:
var i := k - 1;
while (i >= 0) and (indices[i] = n - k + i) do
i := i - 1;
// комбинации закончились:
if i < 0 then break;
indices[i] := indices[i] + 1;
for var j := i + 1 to k - 1 do
indices[j] := indices[j - 1] + 1;
end;
end;
Мы опять разбиваем задачу на две части, чтобы легче ориентироваться в
коде и спонтанно передрать код из одной части решения в другую – отдельно для Гарри и Тома, что разумно и вытекает струёй из условия задачи.
280
Для надёжного хранения цифр и квадратов чисел определяем множества:
// Гарри
procedure Harry();
begin
// множество допустимых цифр для Гарри:
var digits := new HashSet<char>('01234');
// множество подходящих квадратов:
var squares := new HashSet<string>();
Более подробно и сначала мы круто разберёмся с Гарри, у которого цифры
меньше, значит, он не столь опасен, как Том – даже без Джерри.
Легко установить, что наименьшее пятизначное число Гарри – 10000, а
наибольшее – 44444. Переменная цикла должна принять на себя лимиты от
и до квадратных корней этих чисел, чтобы квадраты пришлись тютелька в
тютельку.
// ищем среди чисел, которые дают пятизначные квадраты:
var minNum := ISqrt(10000); // 100
var maxNum := ISqrt(44444); // 210
Понятно, что ни одно число не начнётся с нуля, а вот последнюю цифру придётся строго проверить!
В цикле for мы находим квадраты чисел в строковом исполнении, чтобы не
утруждать себя излишними манипуляциями с цифрами чисел.
Убеждаемся, что последняя цифра не нуль:
for var i := minNum to maxNum do begin
// пятизначные квадраты:
var sn := (i * i).ToString();
// последняя цифра не 0:
if (sn[^1] = '0') then continue;
281
Убедиться в этом можно и с числом, но нужно ещё тщательней проверить,
что все цифры на месте. Со строкой это легче, чем проще.
Мы нашли квадрат числа, который годится и годен для условия задачи. Отправляем его в множество скверов. Там ему будет совсем не скверно.
// не все цифры:
if not sn.All(digits.Contains) then continue;
// добавляем квадрат в множество:
squares.Add(sn);
// печатаем квадрат на экране:
Writeln($' Гарри: {i}^2 = {sn}')
end;
Каждая цифра должна встречаться уникальное число раз – от пяти до девяти. Сумма этих чисел равна тридцати пяти, а чисел пять, то есть мы
должны выбрать 7 чисел.
// каждая цифра должна встречаться 5, 6, 7, 8 или 9 раз:
var r: HashSet<int> := [ 5, 6, 7, 8, 9 ];
// нужно выбрать 7 чисел
// каждое число из 5 цифр → 35 / 5 = 7
var n := r.Sum div 5;
И вот пошла сугубая комбинаторика! Из множества квадратов мы выбираем все семёрки чисел с помощью новой функции GetCombinations. Она
получает массив строковых представлений чисел и количество элементов
в комбинации.
// перебираем все комбинации из 7 чисел:
foreach var combo in GetCombinations(squares.ToArray, n) do begin
Создаём словарь: цифра, частота в комбинации чисел.
Теперь мы проверяем, что частота цифр полностью совпадает с указанной
в условии, то есть по одному разу от пяти до девяти.
282
// собираем числа в строку:
var combined := string.Join('', combo);
// считаем, сколько раз встречается каждая цифра
// в словаре получаем пары: цифра, частота:
var counter := combined.GroupBy(c -> c)
.ToDictionary(g -> g.Key, g -> g.Count());
Для порядка инвентаризации сортируем все проходные комбинации из
семи чисел. Невыбранные числа – это разность множеств квадратов и комбинации:
// проверяем: множество частот должно совпадать с {5,6,7,8,9}
if (counter.Values
.ToHashSet()
.SetEquals(r)) then
begin
// сортируем числа:
var sortedChosen := combo.OrderBy(x -> x);
// находим невыбранные числа как разность множеств:
var notChosen := squares.Except(combo)
.OrderBy(x -> x);
Эх, ухнули! А дальше сама пойдёт. Припечатываем Гаррика к экрану.
// печатаем ответ для Гарри:
var str := string.Join(', ', sortedChosen);
Writeln($' Гарри выбрал: {str}');
Writeln($' Гарри не выбрал: {notChosen.First}');
Writeln;
end;
end;
end;
Задача для Тома решается аналогично:
// Том
283
procedure Tom();
begin
// множество допустимых цифр для Тома:
var digits := new HashSet<char>('56789');
// множество подходящих квадратов:
var squares := new HashSet<string>();
// перебираем числа, которые дают пятизначные квадраты:
var minNum := ISqrt(50000); // 236
var maxNum := ISqrt(99999); // 316
for var i := minNum to maxNum do begin
var sn := (i * i).ToString();
if not sn.All(digits.Contains) then continue;
squares.Add(sn);
// печатаем квадрат на экране:
Writeln($' Том: {i}^2 = {sn}')
end;
// каждая цифра должна встречаться 1, 2, 3 или 4 раза:
var r: HashSet<int> := [ 1, 2, 3, 4 ];
// нужно выбрать 2 числа
// каждое число из 5 цифр → 10 / 5 = 2
var n := r.Sum() div 5;
// перебираем все комбинации из 2-х чисел:
foreach var combo in GetCombinations(squares.toArray, n) do begin
var combined := string.Join('', combo);
var counter := combined.GroupBy(c -> c)
.ToDictionary(g -> g.Key, g -> g.Count());
// проверяем: множество частот должно совпадать с {1, 2, 3, 4}
if counter.Values.ToHashSet().SetEquals(r) then begin
var sortedChosen := combo.OrderBy(x -> x);
var notChosen := squares.Except(combo).OrderBy(x -> x);
// печатаем ответ для Тома:
var str := string.Join(', ', sortedChosen);
Writeln($' Том выбрал: {str}');
str := string.Join(', ', notChosen);
Writeln($' Том не выбрал: {str}');
Writeln;
end;
end;
end;
284
begin
Writeln(' Enigma 1504: All ten digits');
Writeln;
// отвечаем на вопрос 1:
Harry();
// отвечаем на вопрос 2:
Tom();
end.
Не будем крутить динаму и торпеду, а категорично запустим программу.
Что мы видим – ни словами сказать, ни пером описать. Это надо просто видеть в упор глазами лёжа:
Enigma 1504: All ten digits
Гарри: 101^2 = 10201
Гарри: 102^2 = 10404
Гарри: 111^2 = 12321
Гарри: 149^2 = 22201
Гарри: 152^2 = 23104
Гарри: 179^2 = 32041
Гарри: 182^2 = 33124
Гарри: 201^2 = 40401
Гарри выбрал: 10201, 10404, 12321, 23104, 32041, 33124, 40401
Гарри не выбрал: 22201
Том: 236^2 = 55696
Том: 264^2 = 69696
Том: 313^2 = 97969
Том: 314^2 = 98596
Том: 316^2 = 99856
Том выбрал: 55696, 97969
Том не выбрал: 69696, 98596, 99856
И тут, и тогда вы должны оценить порядочную сложность задачи для решения. А ведь её ещё нужно было придумать…
285
Проект Enigma 1518: Sets of squares
Исходный код программы находится в файле Sets of squares.pas.
В журнале New Scientist #2680 от 1-го октября 2008 года была напечатана
задача Enigma 1518:
Задача номер 1518 называется Sets of squares.
(1) Find the set of perfect squares that between them use each of the digits 1 to 9
exactly once whose sum is as small as possible. What is the sum of your set of
squares?
(2) Find the set of perfect squares that between them use each of the digits 0 to 9
exactly once whose sum is as small as possible. (Note: 0 must be used as a digit in a
square, not as a square by itself and not as a leading digit). What is the sum of your
set of squares?
По-русски её смысл можно передать такими словами.
(1) Найдите множество квадратов чисел, в которых каждая цифра от 1 до
9 встречается ровно 1 раз, с наименьшей суммой.
Какова сумма квадратов этого множества?
(2) Найдите множество квадратов чисел, в которых каждая цифра от 0 до
9 встречается ровно 1 раз, с наименьшей суммой.
(Примечание: 0 должен использоваться как цифра в квадрате, а не как самостоятельный квадрат и не как первая цифра)
Какова сумма квадратов этого множества?
Как говорится, или пан, или пан дигитальных чисел. Первый про пан для
нас неприемлем, а второй – как раз про нас.
286
Эта задача снова возвращает нас к пандигитальной теме, от которой мы
далеко и не отходили, а всегда были рядом или около.
Цельная проблема скверов состоит из двух частей, которые ясно и очевидно решаются одинаково, исключая проверку квадратов – то ли есть
нуль, а то ли его нет.
Для каждой части программистского дела нам нужно найти набор, или множество квадратов чисел без повторяющихся цифр (то есть все цифры в
наборе должны быть разными).
В целом числа в наборе должны содержать все цифры от единицы до девяти – первый случай, или от нуля до девяти – второй случай. Если таких
наборов окажется несколько, то нужно напечатать набор с наименьшей
суммой чисел. Надеюсь, я всё верно и понятно объяснил.
С некоторым напряжением и напрягом мыслей мы напишем одну процедуру Solve для решения обеих задач.
Она получит от нас число n и функцию для проверки чисел - Check или
Check0.
Uses MathExtensions;
// ЕСТЬ ЛИ В СТРОКЕ ПОВТОРЯЮЩИЕСЯ ЦИФРЫ
function Check(s: string): bool;
begin
var distinctChars := s.Distinct().ToList();
Result := distinctChars.Count <> s.Length;
end;
// ЕСТЬ ЛИ В СТРОКЕ НУЛЬ ИЛИ ПОВТОРЯЮЩИЕСЯ ЦИФРЫ
function Check0(s: string) := s.Contains(‘0’) or Check(s);
В первую очередь, мы собираем в список squares квадраты чисел, прошедших проверку в вышеназванных функциях:
// РЕШАЕМ ЗАДАЧУ
287
procedure Solve(n: int64; check: Func<string, boolean>);
begin
var minSum: int64:= 0;
Writeln($' Решение для n = {n}');
// cобираем в список все квадраты <= n,
// удовлетворяющие условию check
var squares := new List<string>();
var i: int64 := 1;
while true do begin
var sq: int64 := i * i;
if sq > n then break;
var s := sq.ToString();
if check(s) then begin
i += 1;
continue;
end;
squares.Add(s);
i += 1;
end;
Простота закончилась, и началась труднота. Нам нужно по-взрослому решать задачу. В множество targetDigits забиваем заданные цифры, а переменная minSum сохранит минимальную сумму квадратов чисел, то есть ответ на задачу:
// заданные цифры:
var targetDigits := new HashSet<char>(n.ToString().ToCharArray());
minSum := 0;
Отправляем все материалы дела в функцию Find, от которой в переменной
minSum получим числовой ответ на задачу. Но если задача решения не
имеет, то переменная minSum сохранит значение 0.
Функция Find рекурсивная, поэтому трудна для понимания. Простой смысл
её заключается в том, чтобы собрать набор квадратов чисел из списка
squares, в котором содержатся все заданные цифры. Если такой набор будет найден, мы проверим, лучше ли он текущего рекорда в переменной
288
minSum. Если это так, мы обновим рекорд и напечатаем новый рекорд на
экране:
// решаем задачу:
Find(targetDigits, new List<int64>(), 0, squares, minSum);
// РЕКУРСИВНАЯ ФУНКЦИЯ - ИЩЕТ КОМБИНАЦИИ КВАДРАТОВ ДЛЯ ЗАДАННОГО НАБОРА
ЦИФР
procedure Find(digits: HashSet<char>;
sqs: List<int64>;
i: integer;
squares: List<string>;
var minSum: int64);
begin
// все цифры имеются — обновляем минимум:
if digits.Count = 0 then begin
var sum := sqs.Sum();
if (minSum = 0) or (sum < minSum) then begin
minSum := sum;
var s := string.Join(', ', sqs);
Writeln($' {s} => {sum}');
end;
exit;
end;
// перебираем квадраты, начиная с индекса i:
for var j := i to squares.Count - 1 do begin
var sq := squares[j];
// если длина квадрата больше,
// чем осталось цифр, то дальше не ищем
if sq.Length > digits.Count then break;
// gроверяем, что все цифры квадрата содержатся в оставшихся цифрах:
if not sq.All(digits.Contains) then continue;
// hекурсивно продолжаем поиск, убрав использованные цифры:
var remainingDigits := new HashSet<char>(digits.Except(sq.ToCharArray()));
var newSqs := new List<int64>(sqs);
newSqs.Add(StrToInt64(sq));
289
Find(remainingDigits, newSqs, j + 1, squares, minSum);
end;
end;
Если в результате самоотверженной работы функции Find переменная
minSum получит положительное значение, то задача решена:
// печатаем ответ
if minSum <> 0 then
Writeln($' min({n}) = {minSum}')
else
Writeln($' min({n}) = не найдено');
Writeln;
end;
С некоторым трудовым трепетом в груди запускаем программу. Ан нет, всё
нормуль: задача разрешена в двух ипостасях.
В первом случае нужно взять квадраты 1, 9, 25, 36, 784 с суммой 855. Во
втором – квадраты 324, 576, 1089 с суммой 1989.
Вот тебе задача!
Enigma 1518: Sets of squares
Решение для n = 987654321
1, 4, 9, 872356 => 872370
1, 9, 25, 36, 784 => 855
min(987654321) = 855
Решение для n = 9876543210
1, 4, 9, 36, 87025 => 87075
1, 9, 784, 30625 => 31419
1, 36, 784, 9025 => 9846
9, 16, 784, 3025 => 3834
9, 81, 576, 2304 => 2970
324, 576, 1089 => 1989
min(9876543210) = 1989
290
Проект Задача Крайчика #3
Исходный код программы находится в файле Задача Крайчика
#3.pas.
В известной книге бельгийского математика Мориса Крайчика (Maurice
Kraitchik, родился в Минске) Mathematical recreations есть раздел, посвящённый криптарифмам.
Мы решим одну задачу из этой книги:
Задача номер 3 называется Oder. Вы найдёте ею на странице 82.
291
Задача необыкновенно простая и легко решается по аналогии с предыдущими задачами. Мы только должны учесть, что в криптарифме всего 4 неизвестные буквы:
{$reference GenericsCombinatorics.dll}
uses Facet.Combinatorics;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var arr := Range(0, 9).ToArray();
var vars := new Variations<integer>(arr, 4, GenerateOption.WithoutRepetition);
var num := 0;
foreach var v in vars do begin
var o := v[0];
if (o = 0) then continue;
var d := v[1];
if (d = 0) then continue;
var e := v[2];
var r := v[3];
var oder := ((o * 10 + d) * 10 + e) * 10 + r;
if oder <> 18*(d*10 + o + o*10+r) then continue;
// нашли решение:
Writeln($' ODER = {o}{d}{e}{r}');
num += 1;
end;
Writeln($' Всего решений: {num}');
Writeln;
end;
begin
Writeln(' Задача Крайчика #3');
Writeln;
Solve();
end.
И вот единственное решение этой задачи:
292
Задача Крайчика #3
ODER = 1926
Всего решений: 1
Проект Задача Мадахи #22
Исходный код программы находится в файле Задача Мадахи #22.pas.
В не менее известной книге Джозефа Мадахи (Joseph S. Madachy) Mathematics on vacation вся седьмая глава отдана рассказу о криптарифмах.
Большинство криптарифмов – известные классические задачи, но в одной
нужно извлечь квадратный корень:
293
Задача номер 22 называется Rut. Вы найдёте ею на странице 191.
Задача простая, но при проверке чисел мы заменим извлечение корня в левой части равенства возведением в квадрат его в правой части.
{$reference GenericsCombinatorics.dll}
uses Facet.Combinatorics;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var arr := Range(0, 9).ToArray();
var vars := new Variations<integer>(arr, 6, GenerateOption.WithoutRepetition);
var num := 0;
foreach var v in vars do begin
var c := v[0];
if (c = 0) then continue;
var r := v[1];
if (r = 0) then continue;
var a := v[2];
var e := v[3];
var u := v[4];
var t := v[5];
var career := ((((c * 10 + a) * 10 + r) * 10 + e)*10 + e)*10 + r;
var rut := (r * 10 + u) * 10 + t;
if (rut * rut <> career) then continue;
// нашли решение:
Writeln($' CAREER = {c}{a}{r}{e}{e}{r}');
Writeln($' RUT = {r}{u}{t}');
num += 1;
end;
Writeln($' Всего решений: {num}');
Writeln;
end;
294
begin
Writeln(' Задача Мадахи #22');
Writeln;
Solve();
end.
Задача Мадахи решена:
Задача Мадахи #22
CAREER = 376996
RUT = 614
Всего решений: 1
Проект Задача Мартина Гарднера
Исходный код программы находится в файле Задача Мартина Гарднера.pas.
И одна из лучших книг Мартина Гарднера Математические досуги не обошла вниманием криптарифмы.
295
В одной из них также нужно извлечь квадратный корень.
Задача номер 5 называется Wonderful. Вы найдёте ею на странице 66.
В слове WONDERFUL – 9 разных букв, поэтому для решения задачи потребуется некоторое время. Других трудностей и в этой задаче нет.
{$reference GenericsCombinatorics.dll}
uses Facet.Combinatorics;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
var arr := Range(0, 9).ToArray();
var vars := new Variations<integer>(arr, 9, GenerateOption.WithoutRepetition);
var num := 0;
foreach var v in vars do begin
var w := v[0];
if (w = 0) then continue;
var o := v[1];
if (o = 0) then continue;
296
var
var
var
var
var
var
var
n
d
e
r
f
u
l
:=
:=
:=
:=
:=
:=
:=
v[2];
v[3];
v[4];
v[5];
v[6];
v[7];
v[8];
var wonderful := (((((((w * 10 + o) * 10 + n) * 10 + d) * 10 + e) *
10 + r)*10 + f)*10 + u)*10 + l;
var ooddf := (((o * 10 + o) * 10 + d)*10 +d)*10 + f;
if (ooddf * ooddf <> wonderful) then continue;
// нашли решение:
Writeln($' WONDERFUL = {w}{o}{n}{d}{e}{r}{f}{u}{l}');
Writeln($' OODDF = {o}{o}{d}{d}{f} ');
num += 1;
end;
Writeln($' Всего решений: {num}');
Writeln;
end;
begin
Writeln(' Задача Мартина Гарднера');
Writeln;
Solve();
end.
Прекрасная задача распрекрасно решена:
Задача Мартина Гарднера
WONDERFUL = 523814769
OODDF = 22887
Всего решений: 1
297
Проект Высокая степень искусства
Исходный код программы находится в файле Высокая степень искусства.pas.
Число 81 обладает интересной особенностью. Если сумму его цифр возвести в квадрат, то оно же и получится:
(8+1)2 = 81
Двузначных чисел подобного рода больше нет, а трёхзначное число только
одно. Его можно найти и ручным счётом, но как вы относитесь к 18- и более
значным числам? Например, не самое большое число, которое нам вскоре
предстоит найти, вот такое страшное-престрашное:
5728074323261865147583203450623410328657806917765607180144627106058056117
6768011743674603909430893303748991673128983063946133540401714270017038345
9601690707505549445405196125257706023122381146601014719496694543369193737
99537200070656 =
(5+7+2+8+0+7+4+3+2+3+2+6+1+8+6+5+1+4+7+5+8+3+2+0+3+4+5+0+6+2+3+4+1+0+3+2+
8+6+5+7+8+0+6+9+1+7+7+6+5+6+0+7+1+8+0+1+4+4+6+2+7+1+0+6+0+5+8+0+5+6+1+1+7
+6+7+6+8+0+1+1+7+4+3+6+7+4+6+0+3+9+0+9+4+3+0+8+9+3+3+0+3+7+4+8+9+9+1+6+7+
3+1+2+8+9+8+3+0+6+3+9+4+6+1+3+3+5+4+0+4+0+1+7+1+4+2+7+0+0+1+7+0+3+8+3+4+5
+9+6+0+1+6+9+0+7+0+7+5+0+5+5+4+9+4+4+5+4+0+5+1+9+6+1+2+5+2+5+7+7+0+6+0+2+
3+1+2+2+3+8+1+1+4+6+6+0+1+0+1+4+7+1+9+4+9+6+6+9+4+5+4+3+3+6+9+1+9+3+7+3+7
+9+9+5+3+7+2+0+0+0+7+0+6+5+6)78
Ворочать числами мы, безусловно, поручим компьютеру, ну а нам самим
придётся немного поворочать мозгами.
Дабы не мучать программу многочисленными запусками, организуем «бесконечный» ввод данных – показатель степени n (от двух до ста), в которую
нужно возвести изрядный ряд чисел:
begin
Writeln(' Высокая степень искусства');
298
Writeln;
// бесконечный цикл ввода данных // пока пользователь не закроет программу:
repeat
var num := ReadInteger(' Введите степень
Solve(num);
until not (true);
end.
>');
Для работы с большими числами я написал отдельный модуль
BigIntegerExtensions, так что процедура Solve получилась короткой и ясной, как лунная ночь:
uses BigIntegerExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve(n: integer);
begin
Если мы ограничимся поиском не более чем 100-значных чисел, то сумма
цифр в любом из них заведомо меньше тысячи, но для круглости мы поставим тысячу. Для минимальной суммы цифр мы запишем двойку, хотя это
явное преуменьшение:
for var i := 2 to 1000 do begin
Теперь мы последовательно возводим в степень n все числа от 2 до 1000.
То есть находим заданную степень соответствующих сумм цифр. Например,
если показатель степени n=2, то при i = 9 степень окажется равной 81. Степень мы вычисляем с помощью метода Pow класса BigInteger:
var
power := BigInteger.Pow(i, n);
Итак, у нас в руках оказалась степень числа in. Если число power решает
нашу задачу, то сумма его цифр sum должна равняться числу i. Находим её
299
и сравниваем эти два числа. Если они «сходятся», то решение найдено, и мы
печатаем строку со всеми арифметическими выкладками на экране:
// сумма цифр числа:
var sum := power.DigitSumBig;
if (sum = i) then begin
var s := string.Join('+', power.GetDigitsBig);
Writeln($' {power} = ({s})^{n}');
end;
end;
Writeln;
end;
Остепенились по полной программе!
Высокая степень искусства
Введите степень
81 = (8+1)^2
> 2
Введите степень > 3
512 = (5+1+2)^3
4913 = (4+9+1+3)^3
5832 = (5+8+3+2)^3
17576 = (1+7+5+7+6)^3
19683 = (1+9+6+8+3)^3
Введите степень > 4
2401 = (2+4+0+1)^4
234256 = (2+3+4+2+5+6)^4
390625 = (3+9+0+6+2+5)^4
614656 = (6+1+4+6+5+6)^4
1679616 = (1+6+7+9+6+1+6)^4
Введите степень > 5
17210368 = (1+7+2+1+0+3+6+8)^5
52521875 = (5+2+5+2+1+8+7+5)^5
60466176 = (6+0+4+6+6+1+7+6)^5
205962976 = (2+0+5+9+6+2+9+7+6)^5
Введите степень
> 6
300
34012224 = (3+4+0+1+2+2+2+4)^6
8303765625 = (8+3+0+3+7+6+5+6+2+5)^6
24794911296 = (2+4+7+9+4+9+1+1+2+9+6)^6
68719476736 = (6+8+7+1+9+4+7+6+7+3+6)^6
Введите степень > 7
612220032 = (6+1+2+2+2+0+0+3+2)^7
10460353203 = (1+0+4+6+0+3+5+3+2+0+3)^7
27512614111 = (2+7+5+1+2+6+1+4+1+1+1)^7
52523350144 = (5+2+5+2+3+3+5+0+1+4+4)^7
271818611107 = (2+7+1+8+1+8+6+1+1+1+0+7)^7
1174711139837 = (1+1+7+4+7+1+1+1+3+9+8+3+7)^7
2207984167552 = (2+2+0+7+9+8+4+1+6+7+5+5+2)^7
6722988818432 = (6+7+2+2+9+8+8+8+1+8+4+3+2)^7
Введите степень > 8
20047612231936 = (2+0+0+4+7+6+1+2+2+3+1+9+3+6)^8
72301961339136 = (7+2+3+0+1+9+6+1+3+3+9+1+3+6)^8
248155780267521 = (2+4+8+1+5+5+7+8+0+2+6+7+5+2+1)^8
Введите степень > 9
3904305912313344 = (3+9+0+4+3+0+5+9+1+2+3+1+3+3+4+4)^9
45848500718449031 = (4+5+8+4+8+5+0+0+7+1+8+4+4+9+0+3+1)^9
150094635296999121 = (1+5+0+0+9+4+6+3+5+2+9+6+9+9+9+1+2+1)^9
Введите степень > 10
13744803133596058624 = (1+3+7+4+4+8+0+3+1+3+3+5+9+6+0+5+8+6+2+4)^10
19687440434072265625 = (1+9+6+8+7+4+4+0+4+3+4+0+7+2+2+6+5+6+2+5)^10
53861511409489970176 = (5+3+8+6+1+5+1+1+4+0+9+4+8+9+9+7+0+1+7+6)^10
73742412689492826049 = (7+3+7+4+2+4+1+2+6+8+9+4+9+2+8+2+6+0+4+9)^10
179084769654285362176 = (1+7+9+0+8+4+7+6+9+6+5+4+2+8+5+3+6+2+1+7+6)^10
480682838924478847449 = (4+8+0+6+8+2+8+3+8+9+2+4+4+7+8+8+4+7+4+4+9)^10
Введите степень > 11
8007313507497959524352 =
(8+0+0+7+3+1+3+5+0+7+4+9+7+9+5+9+5+2+4+3+5+2)^11
21048519522998348950643 =
(2+1+0+4+8+5+1+9+5+2+2+9+9+8+3+4+8+9+5+0+6+4+3)^11
23316389970546096340992 =
(2+3+3+1+6+3+8+9+9+7+0+5+4+6+0+9+6+3+4+0+9+9+2)^11
Введите степень > 12
2518170116818978404827136 =
(2+5+1+8+1+7+0+1+1+6+8+1+8+9+7+8+4+0+4+8+2+7+1+3+6)^12
301
А вот и страшное число, которым я пугал вас в начале проекта!
Введите степень
> 78
5728074323261865147583203450623410328657806917765607180144627106058056117
6768011743674603909430893303748991673128983063946133540401714270017038345
9601690707505549445405196125257706023122381146601014719496694543369193737
99537200070656 =
(5+7+2+8+0+7+4+3+2+3+2+6+1+8+6+5+1+4+7+5+8+3+2+0+3+4+5+0+6+2+3+4+1+0+3+2+
8+6+5+7+8+0+6+9+1+7+7+6+5+6+0+7+1+8+0+1+4+4+6+2+7+1+0+6+0+5+8+0+5+6+1+1+7
+6+7+6+8+0+1+1+7+4+3+6+7+4+6+0+3+9+0+9+4+3+0+8+9+3+3+0+3+7+4+8+9+9+1+6+7+
3+1+2+8+9+8+3+0+6+3+9+4+6+1+3+3+5+4+0+4+0+1+7+1+4+2+7+0+0+1+7+0+3+8+3+4+5
+9+6+0+1+6+9+0+7+0+7+5+0+5+5+4+9+4+4+5+4+0+5+1+9+6+1+2+5+2+5+7+7+0+6+0+2+
3+1+2+2+3+8+1+1+4+6+6+0+1+0+1+4+7+1+9+4+9+6+6+9+4+5+4+3+3+6+9+1+9+3+7+3+7
+9+9+5+3+7+2+0+0+0+7+0+6+5+6)^78
Проект Числа Армстронга
Исходный код программы находится в файле Числа Армстронга.pas.
n-значное число называется числом Армстронга, если оно равно сумме nых степеней своих цифр. Первое число Армстронга – 153. Проверим это
утверждение:
13 + 53 + 33 = 1 + 125 + 27 = 153
Всё верно!
Первые девять натуральных чисел 1..9 формально также
являются числами Армстронга, но они слишком тривиальны, чтобы быть интересными.
302
По-английски такие числа называют Armstrong numbers
или Pluperfect Digital Invariants – PPDIs.
Другое название этих чисел – нарциссические (Narcissic
numbers). Оно восходит к самовлюблённому Нарциссу и
обыгрывает тот факт, что числа Армстронга так любят
себя, что сумма степеней их цифр равняется им самим.
Всего существует 88 чисел Армстронга (для десятичной системы счисления). Первое из них, как мы уже знаем. Это 153. Мы легко найдём и несколько следующих: 370, 371, 407. Но самое большое состоит из 39 цифр,
так что потрудиться нам предстоит немало. И вот почему. Несмотря на
внешнюю схожесть наших предыдущих степенных чисел и чисел Армстронга, последние отыскать куда сложнее. С другой стороны, степень
суммы натуральных чисел всегда больше суммы их степеней, поэтому числа
Армстронга заканчиваются уже на 39-й степени, а в предыдущем проекте
мы добрались, по крайней мере, до 78-й!
А вот полный список чисел Армстронга:
№
n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
3
3
3
3
4
4
4
5
5
5
6
7
7
7
7
Число Армстронга
153
370
371
407
1634
8208
9474
54748
92727
93084
548834
1741725
4210818
9800817
9926315
303
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
8
8
8
9
9
9
9
10
11
11
11
11
11
11
11
11
14
16
16
17
17
17
19
19
19
19
20
21
21
23
23
23
23
23
24
24
24
25
25
25
25
25
27
24678050
24678051
88593477
146511208
472335975
534494836
912985153
4679307774
32164049650
32164049651
40028394225
42678290603
44708635679
49388550606
82693916578
94204591914
28116440335967
4338281769391370
4338281769391371
21897142587612075
35641594208964132
35875699062250035
1517841543307505039
3289582984443187032
4498128791164624869
4929273885928088826
63105425988599693916
128468643043731391252
449177399146038697307
21887696841122916288858
27879694893054074471405
27907865009977052567814
28361281321319229463398
35452590104031691935943
174088005938065293023722
188451485447897896036875
239313664430041569350093
1550475334214501539088894
1553242162893771850669378
3706907995955475988644380
3706907995955475988644381
4422095118095899619457938
121204998563613372405438066
304
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
27
27
27
27
29
29
29
29
31
31
31
32
33
33
34
35
35
37
38
39
39
121270696006801314328439376
128851796696487777842012787
174650464499531377631639254
177265453171792792366489765
14607640612971980372614873089
19008174136254279995012734740
19008174136254279995012734741
23866716435523975980390369295
1145037275765491025924292050346
1927890457142960697580636236639
2309092682616190307509695338915
17333509997782249308725103962772
186709961001538790100634132976990
186709961001538790100634132976991
1122763285329372541592822900204593
12639369517103790328947807201478392
12679937780272278566303885594196922
1219167219625434121569735803609966019
12815792078366059955099770545296129367
115132219018763992565095597973971522400
115132219018763992565095597973971522401
В таблицу не включены первые девять чисел Армстронга:
1..9.
Для некоторых степеней числа Армстронга отсуствуют.
Числа Армстронга против грубой силы
Сила есть – ума не надо.
Такая пословица
Попробуем взять числа Армстронга недолго думая, что нам вполне удалось
с предыдущими числами.
Пусть нам нужно найти числа Армстронга, которые состоят из n цифр. Мы
последовательно перебираем все числа от 10n-1 до 10n-1 и для каждого
находим сумму n-ной степени его цифр. Если она совпадёт с исходным числом, значит, число Армстронга найдено!
305
Для простоты программирования я добавил в модуль BigIntegerExtensions
два метода расширения:
/// ВОЗВРАЩАЕТ КАЖДУЮЦИФРУ ЧИСЛА В ЗАДАННОЙ СТЕПЕНИ
function GetDigitPowersBig(self: BigInteger; power: integer): sequence of
BigInteger; extensionmethod;
begin
foreach var digit in self.GetDigitsBig do
yield BigInteger.Pow(digit, power);
end;
/// ВОЗВРАЩАЕТ TRUE, ЕСЛИ ЧИСЛО АРМСТРОНГА
function IsArmstrongNumber(self: BigInteger; power: integer): boolean; extensionmethod;
begin
var sum := self.GetDigitPowersBig(power).Sum;
Result := sum = self;
end;
В результате некоторых мозговых усилий и рукоприкладства программа
получилась!
uses MathExtensions, BigIntegerExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve(n: integer);
begin
var a := 10.IPower(n-1);
var b := 10.IPower(n)-1;
for var i := a to b do begin
var num := BigInteger(i);
if num.IsArmstrongNumber(n) then
Writeln($' {num}');
end;
Writeln;
end;
begin
Writeln(' Числа Армстронга');
Writeln;
306
// бесконечный цикл ввода данных // пока пользователь не закроет программу:
repeat
var num := ReadInteger(' Введите длину чисел (3..9)
Solve(num);
until not (true);
end.
>');
Как учит нас правило рычага, если в одном месте что-то короче, то в другом
длиннее. Программа короткая, но работает долго, поэтому я набрался
ждучести только до 8-значных чисел:
Числа Армстронга
Введите длину чисел (3..9)
153
370
371
407
> 3
Введите длину чисел (3..9)
1634
8208
9474
> 4
Введите длину чисел (3..9)
54748
92727
93084
> 5
Введите длину чисел (3..9)
548834
> 6
Введите длину чисел (3..9)
1741725
4210818
9800817
9926315
> 7
Введите длину чисел (3..9)
24678050
24678051
> 8
307
88593477
В этом «лобовом» алгоритме нас особенно должно возмутить постоянное
возведение в степень n одних и тех же чисел 0..9, из которых и состоят все
подозреваемые в нарциссизме числа. Поэтому мы несколько ускорим перебор, если сразу же создадим таблицу степеней 2..39 этих чисел, а при
нахождении суммы степеней будем просто извлекать нужную степень из
массива. Больше ничего хорошего от метода грубой силы ждать не приходится.
procedure Armstrong(n: integer);
begin
var BASE := 10;
var powTable := new int[BASE];
var start := 1;
for var i := 0 to BASE-1 do powTable[i] := i;
for var i := 1 to n-1 do begin
start *= BASE;
for var k := 0 to BASE-1 do powTable[k] *= k;
end;
for var bi := start to start * 10-1 do begin
var sum := 0;
var num := bi;
var digit := 0;
// сумма степеней цифр числа:
while (num > 0) do begin
digit := num mod BASE;
sum += powTable[digit];
// сумма больше исходного числа:
if (sum > bi) then break;
num := num div BASE;
end;
// нашли число Армстронга:
if (num = 0) and (sum = bi) then
Writeln($' {bi}');
end;
Writeln;
end;
308
Смотрим код и результат его работы для 9-значных чисел:
Введите длину чисел (3..9)
146511208
472335975
534494836
912985153
> 9
Для 3-9-значных чисел необходимо перебрать миллиард чисел, и на это уйдёт около 30 секунд времени. Дальше счёт пойдёт на минуты. Если у вас
много свободного времени, то вы сможете продвинуться ещё на пару степеней, но тут уж вам придётся заменить тип данных integer более длинным –
Int64. Сделать это несложно, но грубый алгоритм работает слишком медленно. Неспешность нашего алгоритма объясняется тем, что он по нескольку раз находит сумму степеней одних и тех же чисел. Действительно,
многие числа состоят из одинаковых цифр. Например:
135
153
315
351
513
531
И чем больше в числах цифр, тем больше среди них «анаграмматических».
Но для всей группы таких чисел сумма степеней цифр одинакова, поскольку
одинаковы и сами цифры, а мы вычисляем эту сумму для каждого числа отдельно! Для чисел, состоящих из цифр 1,3,5, - целых 6 раз! Для разработки
нового алгоритма необходимо генерировать все подмножества с повторениями (мультимножества) множества цифр {0,1,2,3,4,5,6,7,8,9}. Для
нашего примера достаточно проверить подмножество цифр {1,3,5}. Ясно,
что из всех чисел, которые можно составить из этих цифр, только одно может быть числом Армстронга (а может и не быть!). К сожалению, программа, реализующая умный алгоритм, гораздо сложнее, поэтому мы удовлетворимся числами Армстронга, состоящими не более чем из девяти
цифр.
309
Словесные дистанции
Мы уже с пользой использовали динамическое программирование для
решения задач. Оно нам надо, поэтому давайте окунёмся в него глубже!
Ошибки совершают все люди, но некоторые из них делают это с особым
удовольствием. Например, так:
1. печатать русские слова латинскими буквами (транслитерация); такие ляпсусы часто появляются при переключении клавиатуры с одного
языка на другой:
ПОЛЕ → GJKT, ПОЛКА → GJKRF
2. переставлять буквы в словах (анаграммы):
ПОЛЕ → ПОЕЛ, РАБА → АРБА
3. заменять одни буквы другими (метаграммы):
ПОЛЕ → ПОЛО, РАБА → БАБА
4. недо/переписывать отдельные буквы (логогрифы):
ПОЛКА → ПОЛА, ПРУТ→ СПРУТ
Чаще всего подобные ошибки возникают при наборе текста на компьютере.
Я, например, едва ли не половину слов набиарю с ощибкеами. Это и понятно:
то соседние клавиши перепутаешь или нажмёшь их одновременно, то дважды стукнешь по одной и той же, то вообще пропустишь или недожмёшь…
Современные текстовые редакторы достаточно внимательны и сообразительны, чтобы отлавливать слова, которых нет в их словаре. Они считают
их ошибочными и подчёркивают красной волнистой линией, а особенно
услужливые программы даже предлагают на выбор список подходящих
слов для исправления.
310
По этому списку легко понять, что компьютер не может догадаться, какое
именно слово вы хотели написать и подбирает похожие на него, а уж выбирать нужное слово – это ваша задача.
Но вот что интересно: а как компьютер находит слова для своего списка? В
следующем проекте мы постараемся проникнуть в суть и глубь этого процесса.
Проект Алгоритм Левенштейна
Исходный код программы находится в файле Алгоритм Левенштейна.pas.
В 1965 году российский учёный Владимир Иосифович
Левенштейн ввёл понятие расстояние редактирования.
Он доказал, что одно слово можно превратить в другое с
помощью трёх операций: вставки, удаления и замены
одного символа. Возможно, эти операции (все или
только некоторые) придётся выполнить многократно.
Минимальное число операций, необходимых для этого,
и называется дистанцией Левенштейна.
311
Иначе его называют алгоритмом Левенштейна, дистанцией Левенштейна (Levenshtein distance) и функцией Левенштейна.
Роль слов могут исполнять строки, тексты, файлы, числа
– вообще любые последовательности, включая молекулы
ДНК.
Для поиска словесных двойняшек нам нужен словарь. Также нам удобнее
начинать индексацию букв в словах с нуля, чтобы сохранить преемственность с другими проектами:
{$zerobasedstrings}
var path := 'OSH-W97frc.txt';
var words: array of string;
Сразу, без промедления загружаем все слова в строковый массив words и
затеваем бесконечный цикл для ввода желаемых слов с клавиатуры. Я полагаю, что двойняшки различаются на 1 сущность, чтобы не получать ненужное детское (изо)обилие:
begin
Writeln(' Алгоритм Левенштейна');
Writeln;
words := ReadAllLines(path);
var n := 1;
while True do begin
var s := ReadString.ToUpper;
Lev(s, n);
end;
end.
312
Таким образом, пользователь сам определяет схожесть слов, которые
нужно подобрать к заданному слову s. Чем больше он задаст дистанцию Левенштейна, тем длиннее окажется список подходящих слов.
Для пользы дела нам потребны 2 функции.
Функция Min3 возвращает меньшее из трёх чисел:
function Min3(a, b, c: integer) := Min(Min(a, b), c);
А функция GetDistance возвращает дистанцию Левенштейна между двумя
заданными словами:
function
begin
var n
var m
if (n
if (m
GetDistance(s,t: string ): integer;
:= s.Length;
:= t.Length;
= 0) then exit(m);
= 0) then exit(n);
// вспомогательный массив:
var distance := new integer[n + 1, m + 1];
var cost := 0;
// заполняем массив исходными данными:
for var i := 0 to n do distance[i, 0] := i;
for var j := 0 to m do distance[0, j] := j;
// вычисляем минимальное расстояние между словами:
for var i := 1 to n do begin
for var j := 1 to m do begin
cost := (t.Substring(j - 1, 1) = s.Substring(i - 1, 1) ? 0 : 1);
distance[i, j] := Min3(distance[i - 1, j] + 1,
distance[i, j - 1] + 1,
distance[i - 1, j - 1] + cost);
end;
end;
Result := distance[n, m];
end;
313
И вот теперь пора и можно дерзить прямо в процедуре Lev, которая получает «ошибочное» слово и дистанцию Левештейна:
// ПОДБИРАЕМ БЛИЗКИЕ СЛОВА ДЛЯ s
procedure Lev(s: string; num: integer);
begin
var str := s + ' >';
Цикл foreach просматривает весь массив слов words и сравнивает каждое
слово w с заданным словом s, вызывая функцию GetDistance, которая и воплощает в жизнь весь алгоритм Левенштейна:
var flg := False;
// просматриваем весь список:
foreach var w in words do begin
var m := GetDistance(w, s);
// нашли похожее слово:
if (m = num) then begin
str += ' ' + w;
flg := True;
end;
end;
if (flg) then
Writeln($' {str}')
else
Writeln($' Похожих слов нет!');
Writeln;
end;
Давайте на примере разберём работу алгоритма. Пусть cлово s = ПОЛКА, а
слово t = ПОЛА. Длина первого слова n = 5, второго – m = 4. По этим данным
мы создаём пустой массив distance с пятью строками и шестью столбцами.
Затем в двух циклах for заполняем нулевую строку и колонку последовательными числами, начиная с нуля:
314
Таблица расстояний в исходном положении
Теперь пришла пора заполнить пустые (конечно, они не совсем пустые,
ведь в них стоят нули) клетки таблицы числами. Мы опять прибегаем к помощи двух циклов for. Внешний проходит по всем колонкам таблицы,
внутренний – по строкам. Для каждой клетки (i, j) мы вычисляем минимальное из трех чисел:
• distance[i - 1, j] + 1 – операция вставки символа
• distance[i, j - 1] + 1 – операция удаления символа
• distance[i - 1, j - 1] + cost - операция замены символа.
Если символы в одинаковых позициях обоих слов совпадают, то cost равняется нулю, в противном случае – единице. Здесь мы должны учесть, что
символы в строке нумеруются с нуля, а соответствующие клетки в массиве
– с единицы:
cost := (t.Substring(j - 1, 1) = s.Substring(i - 1, 1) ? 0 : 1);
315
Начнём с первой пустой клетки (1,1). Поскольку нулевые (по-русски - первые) символы в словах ПОЛА и ПОЛКА совпадают, то cost=0. Для этой
клетки мы должны получить такие значения:
distance[i - 1, j] + 1 → distance[0, 1] + 1- слева от клетки (1,1) = 2
distance[i, j - 1] + 1 → distance[1,0] + 1- сверху от клетки (1,1) = 2
distance[i - 1, j - 1] → distance[0,0] +0 – по диагонали вверх влево от клетки
(1,1) = 0
Теперь нужно минимальное из чисел 2,2,0, то есть нуль записываем в
клетку (1,1):
Нуль в первой клетке
Далее внутренний цикл переводит нас в клетку (1,2). Для неё мы вычисляем
значение аналогично – 3, 1, 2 → 1.
316
Единицу – в клетку ниже
Третьи буквы в словах также совпадают, поэтому продолжаем в том же
духе: 4, 2, 2 → 2.
Двойку – в клетку ниже
317
А вот четвёртые буквы разные, поэтому cost = 1: 5, 3, 4 → 3.
Тройку – в клетку ниже
Первую колонку мы заполнили сверху донизу – переходим ко второй колонке, и так далее – пока не заполним всю таблицу.
Таблица готова
318
Поскольку наши действия ничуть не меняются при нахождении значений
для новых клеток, то вы можете просчитать их самостоятельно.
Последней мы заполнили правую нижнюю клетку, поставив в неё единицу. Она и даёт нам ответ на вопрос, насколько отличаются два заданных
слова друг от друга. Как мы видим, достаточно одной операции, чтобы превратить одно слово в другое. Легко догадаться, что для перевода слова
ПОЛА в слово ПОЛКА следует вставить букву К между буквами Л и А. Для
обратного перевода достаточно удалить букву К из слова ПОЛКА. В трудных
случаях, когда строки имеют большую длину, вычислять дистанцию вручную, конечно, не очень весело, а так, с программой – одно сплошное удовольствие!
Запускаем программу и находим для заданных слов близких «родственников».
Алгоритм Левенштейна
кнока
КНОКА > КНОПКА
фоорма
ФООРМА > ФОРМА
поел
ПОЕЛ > ПОЛ
раба
РАБА > РАБ БАБА ЖАБА РАМА РАНА РАСА РОБА РЫБА
поле
ПОЛЕ > ПОЛ ПОЛА ПОЛК ПОЛО
полка
ПОЛКА > ПОЛА ПОЛК ЗОЛКА КОЛКА ПАЛКА ПИЛКА ПОЛБА ПОПКА ПОРКА ПОЧКА СОЛКА
ХОЛКА ПОИЛКА ПОЛЬКА
прут
ПРУТ > ПЛУТ ПРУД ТРУТ СПРУТ
валера
ВАЛЕРА > ГАЛЕРА
319
Проект Шестизначный перенос
Исходный код программы находится в файле Шестизначный перенос.pas.
Для начала и сугреву мы решим задачу 557 из книги
Математическая шкатулка, страница 94:
Первая слева цифра шестизначного числа – 1.
Если её перенести с первого места в конец числа,
сохранив порядок остальных цифр, то вновь полученное число будет втрое
больше первоначального.
Восстановите первоначальное число.
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// мин. и макс. 6-значные числа:
var min6 := 100000;
var max6 := 299999;
foreach var num in Range(min6, max6) do begin
// первая цифра числа:
var start := num div 100000;
// 5-значное число из последних 5 цифр числа num:
var end5 := num mod 100000;
// новое 6-значное число:
var newnum := end5 * 10 + start;
// это число должно быть втрое больше исходного:
if (newnum <> 3 * num) then
continue;
Writeln($' Число = {num}');
end;
320
Writeln;
end;
begin
Writeln(' Шестизначный перенос');
Writeln;
Solve();
end.
Среди 6-значных чисел, начинающихся с единицы, первоначальное число –
единственное. Есть ещё одно подобное число, но оно начинается с двойки:
Шестизначный перенос
Число = 142857
Число = 285714
Проект Найдите число
Исходный код программы находится в файле Найдите число.pas.
Эта задача вытекает из переборной задачи Шестизначный перенос, которую
мы решили в предыдущем проекте. Там мы искали число, которое увеличивается втрое при переносе первой цифры в конец числа.
В журнале Наука и жизнь, номер 6 за 1964 год, на странице 69 напечатана
интересная задача, которая похожа на Шестизначный перенос, но нужно переставить последнюю цифру - двойку - в начало числа. Казалось бы, разница небольшая, но это не так.
321
К счастью, в том же номере журнала приводится и ответ на эту задачу, который уберёг нас от перебора, ибо жизнь так коротка и прекрасна, что не
стоит тратить её на бездумный перебор.
Легко проверить, что это равенство верное:
Но для лучшего думания и понимания давайте запишем его иначе.
Теперь хорошо видно, что в исходном числе и в произведении совпадают
все цифры, кроме первой и последней. Более того, предпоследняя цифра исходного числа становится последней цифрой произведения. Третья цифра
322
сзади в исходном числе становится предпоследней цифрой в произведении.
И так далее до самой первой цифры исходного числа, которая занимает в
произведении второе место. И наконец, последняя цифра исходного числа
перебирается на первое место в произведении.
Чтобы сделать решение более универсальным, мы используем переменные:
krat – кратность увеличения нового числа при переносе последней цифры
lastnum.
Этот простой приём позволит нам обобщить задачу.
Также мы должны учесть, что число при переносе цифры не может увеличиться более чем в lastnum раз. Например, мы переносим двойку, и она занимает в произведении первое место. Эту двойку мы можем получить
только умножением единицы на двойку. Если бы число увеличилось в 3
раза, тогда при умножении единицы первой цифрой произведения была бы
не двойка, а тройка, что не согласуется с условиями задачи.
begin
Writeln(' Найдите число');
Writeln;
// последняя цифра:
var lastnum := 2;
// во сколько раз увеличивается число:
var krat := 0;
for var i := 2 to lastnum do begin
krat := i;
// число не может увеличиться
// более чем в lastnum раз:
if (krat > lastnum) then begin
Writeln($' Не выполняется условие krat <= lastnum!');
Writeln;
end
else Solve01(krat, lastnum);
end;
end.
323
Всё решение я перенёс в метод Solve01, в который мы передаём значения
переменных krat и lastnum.
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve01(krat, lastnum: int);
begin
Также мы должны помнить, что задача имеет бесконечно много решений,
и мы поступим верно, если будем считать найденные решения и ограничимся только некоторыми из них.
// число решений:
var nSolution := 0;
// макс. число решений:
var maxSolution := 1;// 2;
Поскольку искомые числа очень большие, то для них нужны переменные
типа BigInteger. Сначала число n равно последней цифре числа lastnum.
Также нам нужны переменные nFirst и perenos:
// число:
var n := BigInteger(lastnum);
// первая цифра слева:
var nFirst := lastnum;
// перенос в следующий разряд:
var perenos := 0;
Итак, первоначально искомое число и его последняя (она же - первая)
цифра равны lastnum, то есть двойке, а перенос отсутствует.
Поскольку процесс вычисления многоступенчатый, то мы проводим его в
бесконечном цикле while:
324
// ищем числа в бесконечном цикле:
while (true) do begin
Давайте подумаем, как можно получить последнюю цифру произведения. А очень просто! По условию задачи, последняя цифра в исходном числе
– двойка. Также нам известно, что произведение в 2 раза больше исходного
числа, то есть мы должны умножить его на 2. Но тогда последняя 2 в исходном числе превратится в 4 в произведении. Но последняя цифра произведения – это предпоследняя цифра исходного числа.
Рассуждая аналогично, мы придём к выводу, что следующая цифра слева –
это 4 х 2 = 8, что мы и видим. Двигаемся далее: 8 x 2 = 16. Значит, перед
восьмёркой должна стоять шестёрка. Но обратите внимание, что в результате умножения мы получили число 16, и поэтому 1 должна перейти в следующий разряд. Тогда 6 x 2 + 1 = 13. Очередная цифра – 3, и мы имеем ещё
1 для следующего разряда.
Процесс прост и понятен, и мы можем продолжать его бесконечно. Однако
мы не кощеи бессмертные, поэтому обязаны вовремя остановиться. И остановиться мы должны тогда, когда в числе появится двойка без переноса в
следующий разряд. И тогда мы получим ещё не искомое число, а вот такое:
Чтобы избавиться от первой двойки, на бумаге достаточно просто зачеркнуть её, а в программе – вернуться к предыдущему числу.
По разработанному нами алгоритму мы находим следующую первую цифру
произведения nFirst, умножая текущую первую цифру на число krat и добавляя перенос.
Вычисляем перенос в следующий разряд.
// очередная первая цифра:
nFirst := nFirst * krat + perenos;
perenos := nFirst div 10;
325
И вот здесь нас подстерегает технологическая тонкость: если очередная
первая цифра окажется нулём, то мы не сможем поместить его в начале
числа, поскольку паскаль ведущий нуль не учитывает. Значит, в этом случае
мы должны сразу же найти и следующую первую цифру, которая должна
стоять перед нулём:
// если очередная первая цифра
// равна нулю, то
// сразу вычисляем следующую
// (перед ней):
nFirst := nFirst mod 10;
if (nFirst = 0) then begin
nFirst := perenos * 10;
perenos := 0;
end;
То есть мы одновременно находим 2 начальные цифры произведения. Перенос в следующий разряд, естественно, следует обнулить.
Такая неприятная ситуация может возникнуть, например, если krat равно
пяти, а очередная цифра искомого числа больше 1. Тогда мы получим: 2
(или 4, 6, 8) умножить на 5 равно 10. Обе эти цифры и следует поставить в
начало искомого числа.
Теперь мы можем вычислить текущее значение искомого числа. Пока оно
равно n, но мы должны поставить перед ним одну или две найденные
цифры nFirst. Для этого мы приписываем к ним нужное число нулей, а затем прибавляем текущее значение искомого числа:
// число десятичных разрядов в текущем числе:
var n10 := int(BigInteger.Log10(n) + 1);
// переносим первую цифру в начало числа:
var nShift := BigInteger(nFirst * BigInteger.Pow(10, n10));
// добавляем к текущему числу:
n += nShift;
// оставляем последнюю цифру:
if (nFirst >= 10) then
nFirst := nFirst div 10;
326
// число без последней цифры:
var nBezLast := BigInteger(n div 10);
// умножаем последнюю цифру:
n10 := int(BigInteger.Log10(n));
var nKrat := BigInteger(lastnum * BigInteger.Pow(10, n10));
// добавляем число без последней цифры:
var nNew := BigInteger(nKrat + nBezLast);
Проверяем не найдено ли решение. И в случае успеха, печатаем его на
экране.
if (nNew = n * krat) then begin
nSolution += 1;
Writeln($' Решение {nSolution}: {n} * {krat} = {nNew}');
// достаточно решений:
if (nSolution = maxSolution) then
break;
end;
end;
Writeln;
end;
Запускаем программу и тут же получаем 2 решения задачи.
Найдите число
Решение 1: 105263157894736842 * 2 = 210526315789473684
Решение 2: 105263157894736842105263157894736842 * 2 =
210526315789473684210526315789473684
Первое решение нам уже известно из журнала, а второе получается из первого простым повторением искомого числа. Точно так же вы можете «утроить» или «учетверить» короткое искомое число и получить новые решения.
Так как при переносе двойки число может увеличиться только в 2 раза, то
никаких других решений для книжной задачи не существует.
327
Если последняя цифра числа тройка, то число может увеличиться в 2 или 3
раза:
157894736842105263 * 2 = 315789473684210526
1034482758620689655172413793 * 3 = 3103448275862068965517241379
Для четвёрки мы получим 3 решения:
210526315789473684 * 2 = 421052631578947368
1379310344827586206896551724 * 3 = 4137931034482758620689655172
102564 * 4 = 410256
Для пятёрки - 4 решения:
263157894736842105 * 2 = 526315789473684210
1724137931034482758620689655 * 3 = 5172413793103448275862068965
128205 * 4 = 512820
102040816326530612244897959183673469387755 * 5 =
510204081632653061224489795918367346938775
Для шестёрки - 5 решений:
315789473684210526 * 2 = 631578947368421052
2068965517241379310344827586 * 3 = 6206896551724137931034482758
153846 * 4 = 615384
122448979591836734693877551020408163265306 * 5 =
612244897959183673469387755102040816326530
1016949152542372881355932203389830508474576271186440677966 * 6 =
6101694915254237288135593220338983050847457627118644067796
Для семёрки - 6 решений:
368421052631578947 * 2 = 736842105263157894
2413793103448275862068965517 * 3 = 7241379310344827586206896551
179487 * 4 = 717948
142857 * 5 = 714285
1186440677966101694915254237288135593220338983050847457627 *
7118644067796610169491525423728813559322033898305084745762
6
=
328
1014492753623188405797 * 7 = 7101449275362318840579
Для восьмёрки - 7 решений:
421052631578947368 * 2 = 842105263157894736
2758620689655172413793103448 * 3 = 8275862068965517241379310344
205128 * 4 = 820512
163265306122448979591836734693877551020408 * 5 =
816326530612244897959183673469387755102040
1355932203389830508474576271186440677966101694915254237288 * 6 =
8135593220338983050847457627118644067796610169491525423728
1159420289855072463768 * 7 = 8115942028985507246376
1012658227848 * 8 = 8101265822784
Для девятки - 8 решений:
473684210526315789 * 2 = 947368421052631578
3103448275862068965517241379 * 3 = 9310344827586206896551724137
230769 * 4 = 923076
183673469387755102040816326530612244897959 * 5 =
918367346938775510204081632653061224489795
1525423728813559322033898305084745762711864406779661016949 * 6 =
9152542372881355932203389830508474576271186440677966101694
1304347826086956521739 * 7 = 9130434782608695652173
1139240506329 * 8 = 9113924050632
10112359550561797752808988764044943820224719 * 9 =
91011235955056179775280898876404494382022471
Любопытно, что некоторые искомые числа можно отыскать среди дробей:
2/19
3/19
4/19
5/19
6/19
7/19
8/19
9/19
=
=
=
=
=
=
=
=
0.105263157894736842…
0.157894736842105263…
0.210526315789473684…
0.263157894736842105…
0.315789473684210526…
0.368421052631578947…
0.421052631578947368…
0.473684210526315789…
329
Так обычной точности не хватает для вычисления этих дробей, то мы используем тип Decimal:
procedure Solve;
begin
foreach var i in range(2,9) do begin
var n1 := Decimal(i);
var n2 := Decimal(19);
var n3 := (n1 / n2).ToString.Substring(2, 18);
Writeln(n3);
end;
Writeln;
end;
Легко заметить, что период всех дробей равен 18, поэтому, чтобы получить
целые числа, дроби следует умножить на 1018:
105263157894736842
157894736842105263
210526315789473684
263157894736842105
315789473684210526
368421052631578947
421052631578947368
473684210526315789
При переносе последней цифры в начало числа оно увеличится в 2 раза.
Аналогично можно найти числа, которые увеличиваются в 3 раза при переносе
последней цифры:
foreach var i in range(3,9) do begin
var n1 := Decimal(i);
var n2 := Decimal(29);
var n3 := (n1 / n2).ToString.Substring(2, 28);
Writeln(n3);
end;
Writeln;
330
1034482758620689655172413793
1379310344827586206896551724
1724137931034482758620689655
2068965517241379310344827586
2413793103448275862068965517
2758620689655172413793103448
3103448275862068965517241379
В 4 раза:
foreach var i in range(4,9) do begin
var n1 := Decimal(i);
var n2 := Decimal(39);
var n3 := (n1 / n2).ToString.Substring(2, 6);
Writeln(n3);
end;
Writeln;
102564
128205
153846
179487
205128
230769
В 5 раз:
102041
122449
142857
163265
183673
Точность типа Decimal ограничена 28-29-ью цифрами, поэтому добавим к
проекту библиотеку ExtendedNumerics.BigDecimal.
331
В 6 раз:
{$reference ExtendedNumerics.BigDecimal.dll}
uses ExtendedNumerics;
BigDecimal.Precision := 100;
foreach var i in range(6,9) do begin
var n1 := BigDecimal(i);
var n2 := BigDecimal(59);
var n3 := (n1 / n2).ToString.Substring(2, 58);
Writeln(n3);
end;
Writeln;
1016949152542372881355932203389830508474576271186440677966
1186440677966101694915254237288135593220338983050847457627
1355932203389830508474576271186440677966101694915254237288
1525423728813559322033898305084745762711864406779661016949
В 7 раз:
foreach var i in range(7,9) do begin
var n1 := BigDecimal(i);
var n2 := BigDecimal(69);
var n3 := (n1 / n2).ToString.Substring(2, 22);
Writeln(n3);
end;
Writeln;
1014492753623188405797
1159420289855072463768
1304347826086956521739
В 8 раз:
foreach
var
var
var
var i
n1 :=
n2 :=
n3 :=
in range(8,9) do begin
BigDecimal(i);
BigDecimal(79);
(n1 / n2).ToString.Substring(2, 13);
332
Writeln(n3);
end;
Writeln;
1012658227848
1139240506329
В 9 раз:
foreach var i in range(9,9) do begin
var n1 := BigDecimal(i);
var n2 := BigDecimal(89);
var n3 := (n1 / n2).ToString.Substring(2, 44);
Writeln(n3);
end;
Writeln;
10112359550561797752808988764044943820224719
Так как при переносе последней цифры n число увеличивается не более чем
в n раз, то общее количество чисел каждый раз уменьшается на 1. Числитель дроби равен последней цифре числа, а знаменатель – 19, 29, 39,…, 89,
то есть 10 * n – 1. А вот длину периода можно найти только опытным путём.
Проект Паразитические числа
Исходный код программы находится в файле Паразитические
числа.pas.
В англоязычной литературе рассмотренные нами в предыдущем проекте
числа называются паразитическими (parasitic numbers, parasite numbers).
Вы можете прочитать о них в книге Клиффорда Пиковера Wonders of Numbers Adventures in Mathematics, Mind, and Meaning, в Главе 80. Например, числа,
увеличивающиеся в 2 раза при переносе последней цифры, называются 2333
parasite. В 3 раза – 3-parasite. И так далее. Ассоциации цифр и чисел с биологическими паразитами слишком натянуты, чтобы принимать их всерьёз.
Настоящими паразитическими числами считаются такие, которые увеличиваются (или уменьшаются) в k раз, если последняя цифра n = k. Если k
отличается от n, то числа называются псевдопаразитическими
(pseudoparasite numbers, pseudoparasites).
Интересный материал о паразитных числах вы найдёте
также на сайте Wikipedia.
Пиковер предлагает вычислить дробь 137174210 / 1111111111 и найти в
ней что-то особенное.
Программа, понятно, очень простая:
334
{$reference ExtendedNumerics.BigDecimal.dll}
uses ExtendedNumerics;
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
BigDecimal.Precision := 50;
var n1 := BigDecimal(137174210);
var n2 := BigDecimal(1111111111);
var n3 := (n1 / n2);
Writeln(' ' + n3);
Writeln;
end;
begin
Writeln(' Паразитические числа');
Writeln;
Solve;
end.
А дробь, действительно, очень любопытная! В ней бесконечно повторяются
все цифры по порядку:
Паразитические числа
0.123456789012345678901234567890123456789012345678901
Клиффорд Пиковер предлагает найти и ультрапаразитов, под которыми
он подразумевает числа, которые увеличиваются в несколько раз при перемещении и первой и последней цифры. В книге нет примеров таких чисел.
Существуют ли они – неизвестно.
Если первую и последнюю цифры просто поменять местами, то они должны быть равны, но при этом исходное
число не изменится.
335
В журнале Наука и жизнь №11 за 1971 год, на странице 118 напечатана заметка:
Этот пример – почти ответ на задачу Пиковера, но в нём первая и последняя
цифры не меняются местами, а добавляются новые цифры в начало и конец
числа.
В журнале Наука и жизнь, №2 за 1972 год, на странице 142 напечатана заметка о паразитических числах:
336
Легко заметить, что в ней присутствуют числа, начинающиеся с нуля, которые мы не учитывали.
Там же сообщается:
Этот проект показывает, что иногда и паразиты могут быть полезны, если
правильно приложить их к больному месту. Правда, медицина здесь бессильна, но математика даёт пылу и жару ещё более интересным числам.
Проект Циклические числа
Исходный код программы находится в файле Циклические
числа.pas.
337
Начнём с известного примера циклических чисел (cyclic numbers). Если последовательно умножать число 142857 на числа 1..6, то мы получим такие
произведения:
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
var n := 142857;
foreach var i in range(1,6) do
Println($' {i} * {n} = {n * i}');
Writeln;
end;
begin
Writeln(' Циклические числа');
Writeln;
Solve;
end.
1 * 142857 = 142857
2 * 142857 = 285714
3 * 142857 = 428571
4 * 142857 = 571428
5 * 142857 = 714285
6 * 142857 = 857142
Можно заметить, что все числа состоят из одних и тех же цифр, но расположенных иначе.
Чтобы ещё более прояснить ситуацию, давайте
запишем числа по кругу. Если читать цифры по
часовой стрелке, начиная с 1, то мы получим
число 142857. Если начать с 2, то получим
число 285714. И так далее. Следовательно, все
произведения состоят из одних и тех же цифр,
записанных по кругу, то есть циклически.
338
Менее известен пример с числом 588235294117647:
var m: int64 := 588235294117647;
foreach var i in range(1,16) do
Println($' {i} * {m} = {m * i}');
Writeln;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
588235294117647
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
0588235294117647
1176470588235294
1764705882352941
2352941176470588
2941176470588235
3529411764705882
4117647058823529
4705882352941176
5294117647058823
5882352941176470
6470588235294117
7058823529411764
7647058823529411
8235294117647058
8823529411764705
9411764705882352
Здесь 16 произведений, полученных из циклически переставляемых цифр.
Оба этих циклических числа представляют собой период бесконечных циклических дробей 1/7 и 1/17:
procedure Solve2;
begin
BigDecimal.Precision := 6;
var n1 := BigDecimal(1);
var n2 := BigDecimal(7);
var n3 := (n1 / n2);
Writeln(' ' + n3);
Writeln;
BigDecimal.Precision := 15;
339
n1 := BigDecimal(1);
n2 := BigDecimal(17);
n3 := (n1 / n2);
Writeln(' ' + n3);
Writeln;
end;
0.142857
0.0588235294117647
Длина цикла на 1 меньше знаменателя, то есть 6 в первом случае и 16 – во
втором.
Все циклические числа можно представить в форме:
Здесь:
10𝑝−1 − 1
𝑝
p – простое число
Для p = 7 имеем:
999999/7 = 142857
Для p = 17 имеем:
9999999999999999/17 = 588235294117647
Но не все простые числа порождают циклические числа. А всего около
37,395%. Из первой тысячи чисел дают циклические числа только эти простые числа:
7, 17, 19, 23, 29, 47, 59, 61, 97, 109, 113, 131, 149, 167, 179, 181, 193, 223, 229, 233,
257, 263, 269, 313, 337, 367, 379, 383, 389, 419, 433, 461, 487, 491, 499, 503, 509,
541, 571, 577, 593, 619, 647, 659, 701, 709, 727, 743, 811, 821, 823, 857, 863, 887,
937, 941, 953, 971, 977, 983.
Циклические числа обладают интересными свойствами:
340
14 + 28 + 57 = 99
142 + 857 = 999
8572 – 1422 = 714285
4117647
58823529
------------99999999
Все циклические числа, исключая 142857, начинаются с 0:
(106-1)
(1016-1)
(1018-1)
(1022-1)
(1028-1)
(1046-1)
(1058-1)
(1060-1)
/
/
/
/
/
/
/
/
7
17
19
23
29
47
59
61
=
=
=
=
=
=
=
=
142857 (6 цифр)
0588235294117647 (16 цифр)
052631578947368421 (18 цифр)
0434782608695652173913 (22 цифр)
0344827586206896551724137931 (28 цифр)
0212765957446808510638297872340425531914893617 (46 цифр)
0169491525423728813559322033898305084745762711864406779661 (58 цифр)
016393442622950819672131147540983606557377049180327868852459 (60 цифр)
Мы легко проверим, что число 142857 – циклическое, но в следующем циклическом числе – 0588235294117647 – уже 16 цифр, за перестановкой которых наблюдать интересно, но трудно.
Как показывает список выше, каждое новое циклическое число становится
всё длиннее. И для простого числа 97 соответствующее число состоит из 96
цифр:
0103092783505154639175257731958762886597938144329896907216494845360
82474226804123711340206185567
Проверять такие числа вручную крайне неостроумно, поэтому мы напишем
простую программу, которая всё сделает за нас – быстро и аккуратно:
function Test(snum: string): bool;
begin
// число:
var num := BigInteger.Parse(snum);
// длина числа:
var numlen := snum.Length;
println($' Длина цикла = {numlen}');
341
// цикл:
var cycle := snum * 2;
println($' Цикл: {cycle}');
// проверяем произведения:
foreach var i in range(2, numlen) do begin
var mult := num * i;
var smult := mult.ToString;
// не циклическое число:
if smult not in cycle then exit(False);
end;
// циклическое число:
Result := True;
end;
procedure Solve3;
begin
var snum := '142857';
snum := '0588235294117647';
snum :=
'016393442622950819672131147540983606557377049180327868852459';
snum :=
'010309278350515463917525773195876288659793814432989690721649484536082474
226804123711340206185567';
var res := Test(snum);
if (res) then
println($' Число {snum} - циклическое')
else
println(' Число {snum} - не циклическое');
end;
Так как почти все циклические числа начинаются с нуля, который паскаль
отбросит, то в тестовую функцию нужно передавать строковое представление числа. С помощью метода расширения Parse мы превратим строку в
число, которое дальше и умножим на все числа от 2 до длины заданного
числа. Например, циклическое число 142 857 мы умножим на числа 2..6.
Все произведения – это циклические перестановки исходного числа, поэтому число 142 857 – циклическое:
285714
428571
342
571428
714285
857142
Число 142 857 – циклическое
Циклические числа
Длина цикла = 6
Цикл: 142857142857
Число 142857 - циклическое
Длина цикла = 16
Цикл: 05882352941176470588235294117647
Число 0588235294117647 - циклическое
Длина цикла = 60
Цикл:
0163934426229508196721311475409836065573770491803278688524590163934426229
50819672131147540983606557377049180327868852459
Число 016393442622950819672131147540983606557377049180327868852459 циклическое
И даже огромное число
0103092783505154639175257731958762886597938144329896907216494845360
82474226804123711340206185567
наша функция проверит в один момент:
Длина цикла = 96
Цикл:
0103092783505154639175257731958762886597938144329896907216494845360824742
2680412371134020618556701030927835051546391752577319587628865979381443298
9690721649484536082474226804123711340206185567
Число
0103092783505154639175257731958762886597938144329896907216494845360824742
26804123711340206185567 - циклическое
343
Итак, в функции Test мы умножаем заданное число num на числа 2... Вопрос
в том, как проверить, что все произведения - это циклические перестановки
исходного числа.
Мы имеем строковое представление циклического числа (или кандидата в
такие числа). Если мы удвоим эту строку, то есть повторим её дважды, то
все циклические перестановки будут подстроками этой длинной строки.
Опять вернёмся к короткому числу 142857 и «удвоим» его:
142857142857
Все произведения содержатся в нём:
285714 142857142857
428571 142857142857
571428 142857142857
714285 142857142857
857142 142857142857
Действительно, если мы обходим число по кругу, то второй круг – это повторение исходного числа. Таким образом, мы можем проверить любое простое число на цикличность.
Самое большое из простых чисел до 1000 порождает вот такое циклическое
число:
0010172939979654120040691759918616480162767039674465920651068158697
8636826042726347914547304170905391658189216683621566632756866734486
2665310274669379450661241098677517802644964394710071210579857578840
2848423194303153611393692777212614445574771108850457782299084435401
8311291963377416073245167853509664292980671414038657171922685656154
6286876907426246185147507629704984740590030518819938962360122075279
7558494404883011190233977619532044760935910478128179043743641912512
7161749745676500508646998982706002034587995930824008138351983723296
0325534079348931841302136317395727365208545269582909460834181078331
6378433367243133265513733468972533062054933875890132248219735503560
5289928789420142421159715157680569684638860630722278738555442522889
1149542217700915564598168870803662258392675483214649033570701932858
344
5961342828077314343845371312309257375381485249237029501525940996948
1180061037639877924720244150559511698880976602238046795523906408952
18718209562563580874872838250254323499491353
И оно именно циклическое, в чём нас убеждает наша функция Test:
Длина цикла = 982
Цикл:
0010172939979654120040691759918616480162767039674465920651068158697863682
6042726347914547304170905391658189216683621566632756866734486266531027466
9379450661241098677517802644964394710071210579857578840284842319430315361
1393692777212614445574771108850457782299084435401831129196337741607324516
7853509664292980671414038657171922685656154628687690742624618514750762970
4984740590030518819938962360122075279755849440488301119023397761953204476
0935910478128179043743641912512716174974567650050864699898270600203458799
5930824008138351983723296032553407934893184130213631739572736520854526958
2909460834181078331637843336724313326551373346897253306205493387589013224
8219735503560528992878942014242115971515768056968463886063072227873855544
2522889114954221770091556459816887080366225839267548321464903357070193285
8596134282807731434384537131230925737538148524923702950152594099694811800
6103763987792472024415055951169888097660223804679552390640895218718209562
5635808748728382502543234994913530010172939979654120040691759918616480162
7670396744659206510681586978636826042726347914547304170905391658189216683
6215666327568667344862665310274669379450661241098677517802644964394710071
2105798575788402848423194303153611393692777212614445574771108850457782299
0844354018311291963377416073245167853509664292980671414038657171922685656
1546286876907426246185147507629704984740590030518819938962360122075279755
8494404883011190233977619532044760935910478128179043743641912512716174974
5676500508646998982706002034587995930824008138351983723296032553407934893
1841302136317395727365208545269582909460834181078331637843336724313326551
3733468972533062054933875890132248219735503560528992878942014242115971515
7680569684638860630722278738555442522889114954221770091556459816887080366
2258392675483214649033570701932858596134282807731434384537131230925737538
1485249237029501525940996948118006103763987792472024415055951169888097660
223804679552390640895218718209562563580874872838250254323499491353
Число
0010172939979654120040691759918616480162767039674465920651068158697863682
6042726347914547304170905391658189216683621566632756866734486266531027466
9379450661241098677517802644964394710071210579857578840284842319430315361
1393692777212614445574771108850457782299084435401831129196337741607324516
7853509664292980671414038657171922685656154628687690742624618514750762970
4984740590030518819938962360122075279755849440488301119023397761953204476
345
0935910478128179043743641912512716174974567650050864699898270600203458799
5930824008138351983723296032553407934893184130213631739572736520854526958
2909460834181078331637843336724313326551373346897253306205493387589013224
8219735503560528992878942014242115971515768056968463886063072227873855544
2522889114954221770091556459816887080366225839267548321464903357070193285
8596134282807731434384537131230925737538148524923702950152594099694811800
6103763987792472024415055951169888097660223804679552390640895218718209562
563580874872838250254323499491353 - циклическое
7, 17, 19, 23, 29, 47, 59, 61, 97, 109, 113, 131, 149, 167, 179, 181, 193, 223, 229, 233,
257, 263, 269, 313, 337, 367, 379, 383, 389, 419, 433, 461, 487, 491, 499, 503, 509,
541, 571, 577, 593, 619, 647, 659, 701, 709, 727, 743, 811, 821, 823, 857, 863, 887,
937, 941, 953, 971, 977, 983.
Если мы возьмём другие простые числа (не вошедшие в список выше), то
убедимся, что период дроби 1/p короче, чем у простых чисел, порождающих
числа циклические:
11 – 0.09… (должно быть 10)
13 – 0.076923 (должно быть 12)
Опять же – проверять вручную долго и утомительно, поэтому мы напишем
небольшую программу, которая самостоятельно проверит все простые
числа в заданном диапазоне:
uses MathExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve;
begin
foreach var p in GeneratePrimes do begin
if p = 2 then continue;
// алгоритм деления в столбик для нахождения периода:
var remainder := 1;
var digits := new List<char>;
var remainders := new Dictionary<int, int>;
var position := 0;
var periodFound := false;
var periodStart := 0;
while not periodFound and (position < 2000) do begin
if remainders.ContainsKey(remainder) then begin
346
periodFound := true;
periodStart := remainders[remainder];
end
else begin
remainders[remainder] := position;
remainder := remainder * 10;
digits.Add(char(ord('0') + remainder div p));
remainder := remainder mod p;
position := position + 1;
end;
end;
if periodFound then begin
var periodLength := digits.Count - periodStart;
if periodLength = p - 1 then begin
Write(' 1/', p:2);
Writeln(' ✓ Циклическое простое!');
end;
end;
if p > 1100 then break;
end;
Writeln;
end;
begin
Writeln(' Циклические числа 2');
Writeln;
Solve;
end.
Она назовёт нам следующие простые числа, порождающие циклические:
Циклические числа 2
1/ 7
1/17
1/19
1/23
1/29
1/47
✓
✓
✓
✓
✓
✓
Циклическое
Циклическое
Циклическое
Циклическое
Циклическое
Циклическое
простое!
простое!
простое!
простое!
простое!
простое!
1/503
1/509
1/541
1/571
1/577
1/593
✓
✓
✓
✓
✓
✓
Циклическое
Циклическое
Циклическое
Циклическое
Циклическое
Циклическое
простое!
простое!
простое!
простое!
простое!
простое!
347
1/59 ✓ Циклическое простое!
1/61 ✓ Циклическое простое!
1/97 ✓ Циклическое простое!
1/109 ✓ Циклическое простое!
1/113 ✓ Циклическое простое!
1/131 ✓ Циклическое простое!
1/149 ✓ Циклическое простое!
1/167 ✓ Циклическое простое!
1/179 ✓ Циклическое простое!
1/181 ✓ Циклическое простое!
1/193 ✓ Циклическое простое!
1/223 ✓ Циклическое простое!
1/229 ✓ Циклическое простое!
1/233 ✓ Циклическое простое!
1/257 ✓ Циклическое простое!
1/263 ✓ Циклическое простое!
1/269 ✓ Циклическое простое!
1/313 ✓ Циклическое простое!
1/337 ✓ Циклическое простое!
1/367 ✓ Циклическое простое!
1/379 ✓ Циклическое простое!
1/383 ✓ Циклическое простое!
1/389 ✓ Циклическое простое!
1/419 ✓ Циклическое простое!
1/433 ✓ Циклическое простое!
1/461 ✓ Циклическое простое!
1/487 ✓ Циклическое простое!
1/491 ✓ Циклическое простое!
1/499 ✓ Циклическое простое!
1/619
1/647
1/659
1/701
1/709
1/727
1/743
1/811
1/821
1/823
1/857
1/863
1/887
1/937
1/941
1/953
1/971
1/977
1/983
1/1019
1/1021
1/1033
1/1051
1/1063
1/1069
1/1087
1/1091
1/1097
1/1103
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
✓ Циклическое простое!
Если вы хотите продолжить поиски, то увеличьте точность дробей.
В журнале Наука и жизнь, №2 за 1968 год, на странице 57 напечатана такая
задача:
348
Ответ на неё – знаменитое число 142 857:
Эта задача связывает паразитические числа с циклическими, так как при
переносе конечной семёрки число увеличивается в 5 раз.
Проект Супержесть
Исходный код программы находится в файле Супержесть.pas.
Эта задача пришла к нам окольным путём из Интернета в виде ютубовского ролика Супержесть для тех, кому скучно. Если кому реально скучно,
то посмотрите на предварительные мозговые мучения, возникшие во
время решения этой задачи рукоголовым способом, чтобы оценить толщину предстоящей нам жести. Даю адресочек:
https://www.youtube.com/watch?v=_6OfnRKtadc
349
Нас, конечно, никакой жестью не запугаешь, но поработать и руками, и
слегка головой тоже – придётся. Но это несравнимо с решением задачи голыми руками.
Замечаем, что нам понадобятся факториалы чисел от четырёх до пятидесяти. В большом деле лучше не мельчить, а сразу вычислить все факториалы и упаковать их в список, из которого всё что угодно извлекать просто
и удобно. Поскольку факториалы стремительно увеличиваются, то они
должны иметь тип BigInteger.
Метод расширения FactorialSequence из модуля BigIntegerExtensions выдаёт последовательность факториалов в заданном количестве:
/// ВЫДАЁТ ПОСЛЕДОВАТЕЛЬНОСТЬ ФАКТОРИАЛОВ
function FactorialSequence(n: integer): sequence of BigInteger;
begin
if n < 0 then
raise new System.ArgumentOutOfRangeException('Число не может быть
отрицательным!');
var fact := BigInteger(1);
yield fact;
for var i := 1 to n do begin
fact := fact * i;
yield fact;
end;
end;
Перед приступом к решению задачи мы получаем в переменной facts список
всех нужных нам факториалов.
uses BigIntegerExtensions;
// РЕШАЕМ ЗАДАЧУ
procedure Solve();
begin
// список факториалов от 0 до 50 включительно:
var facts := 50.FactorialSequence.ToList;
350
В качестве обязательной проверки распечатываем все факториалы на
экране.
foreach var bi in facts do
println(bi);
. . .
begin
Writeln(' Супержесть');
Writeln;
Solve();
end.
В задачном выражении очевидно проглядывает сумма членов ряда, которую выгодно и полезно вычислять в цикле. Переменная цикла, как следует
из задания, изменяется от четырёх до пятидесяти включительно:
// решаем задачу:
var summa := BigInteger.Zero;
for var i := 4 to 50 do begin
Значения членов ряда вычисляются так: переменную цикла умножаем на
неё же, но с добавкой двойки. Это произведение умножаем на квадрат факториала, который извлекаем из списка согласно текущему значению переменной цикла.
var f := facts[i];
Все произведения добавляем к переменной summa, которая изначально
равна нулю:
summa += i * (i + 2) * f * f;
end;
351
Из любопытства и для проверки бдительности смотрим на экране, чего мы
там насуммировали:
Writeln($' Значение выражения = {summa}');
Нас интересуют только 24 последние цифры этого здоровенного числа.
Проще и удобнее отрезать такого размера правоконечный кусок числа из
строки:
// последние 24 цифры:
var str24 := summa.ToString();
str24 := str24.Substring(str24.Length - 24);
Осталось найти сумму двухдюжинной цифири и финишировать с рекордным результатом!
var bi24 := BigInteger.Parse(str24);
println(bi24);
Исключительно для этого и для поддержания спортивной формы мы находим сумму цифр в методе расширения DigitSumBig.
// сумма этих цифр -> ответ на задачу:
var sum := bi24.DigitSumBig;
println(sum);
Writeln;
end;
Тут мы ставим жирную точку, а поверх - печать ответа на экране. И олимпиадной сказочке пришёл конец.
Супержесть
1
352
1
2
6
24
120
720
5040
40320
362880
3628800
39916800
479001600
6227020800
87178291200
1307674368000
20922789888000
355687428096000
6402373705728000
121645100408832000
2432902008176640000
51090942171709440000
1124000727777607680000
25852016738884976640000
620448401733239439360000
15511210043330985984000000
403291461126605635584000000
10888869450418352160768000000
304888344611713860501504000000
8841761993739701954543616000000
265252859812191058636308480000000
8222838654177922817725562880000000
263130836933693530167218012160000000
8683317618811886495518194401280000000
295232799039604140847618609643520000000
10333147966386144929666651337523200000000
371993326789901217467999448150835200000000
13763753091226345046315979581580902400000000
523022617466601111760007224100074291200000000
20397882081197443358640281739902897356800000000
815915283247897734345611269596115894272000000000
33452526613163807108170062053440751665152000000000
1405006117752879898543142606244511569936384000000000
60415263063373835637355132068513997507264512000000000
2658271574788448768043625811014615890319638528000000000
119622220865480194561963161495657715064383733760000000000
353
5502622159812088949850305428800254892961651752960000000000
258623241511168180642964355153611979969197632389120000000000
12413915592536072670862289047373375038521486354677760000000000
608281864034267560872252163321295376887552831379210240000000000
30414093201713378043612608166064768844377641568960512000000000000
Значение выражения =
2405969386799803097354037351136533457791015186104367070023668243919926294
435854489984638737378305808000876543999999999999999999999424
999999999999999999999424
199
354
Литература
[ОО80]
Оре О.
Приглашение в теорию чисел
М.: Наука, 1980 г. - 128 с.
Библиотечка Квант, Выпуск 3
[ЗП88]
Абрамов С.А. и др.
Задачи по программированию
Наука, 1988. – 224 с.
ISBN: 5-02-013774-Х
Серия: Библиотечка программиста
355
[100]
В. А. Дагене, Г. К. Григас, К. Ф. Аугутис
100 задач по программированию
М.:Просвещение, 1993. – 251 с.
ISBN: 5-09-003864-3
[ВНН88]
Воробьёв Н.Н.
Признаки делимости
Наука. - 1988, 96 с.
ISBN 5-02-013731-6
356
[БК85]
Брудно А. Л. Каплан Л. И.
Олимпиады по программированию для
школьников
Наука. - 1985, 96 с.
[ГМ72]
Гарднер Мартин
Математические досуги
М.: Мир, 1972. – 495 с.
357