Flat Preloader Icon

Project Euler (answers)

Проект Эйлера: Ответ и решение 26 задачи (Обратные циклы)

Проект Эйлера (26 задача) потребует лишь знания математики начальных классов. Однако всегда есть возможность ускорить работу программы

Узнать больше в телеграм-канале: ProjectEuler++

Условия задачи

Единичная дробь имеет 1 в числителе. Десятичные представления единичных дробей со знаменателями от 2 до 10 даны ниже:

1/2=0.5
1/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

Где 0.1(6) значит 0.166666…, и имеет повторяющуюся последовательность из одной цифры. Заметим, что 1/7 имеет повторяющуюся последовательность из 6 цифр.

Найдите значение d < 1000, для которого 1/d в десятичном виде содержит самую длинную повторяющуюся последовательность цифр.

Всегда Вам рады)

Решение (проект Эйлера - 26 задача)

Проект Эйлера (26 задача) - решение
Проект Эйлера (26 задача) - решение

Суть алгоритма программы

Поясню суть алгоритма, использованного в программе на примере.

Делим 1 на 6 в столбик, как на школьных уроках:

  • 1 * 10 = 10; // поскольку 1 не делится на 6, потому увеличиваем числитель на порядок.
  • 10 / 6 = 1; // в итоге получаем остаток от деления 10 % 6 = 4)
  • Далее делим остаток от деления
  • 4 * 10 = 40; // снова увеличиваем остаток на порядок
  • 40 / 6 = 6; //получаем остаток 40 % 6 = 4
  • Однако данный остаток уже был! Потому, если продолжать деление, то вычисления просто повторятся
  • 4 * 10 = 40;
  • 40 / 6 = 6;

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

Первое улучшение программы

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

Например, для дроби number / 7, остаток от деления number % 7 может быть 0, 1, 2, 3, 4, 5 и 6, т.е. в итоге будет возможно лишь семь вариантов. Потому через семь итераций либо будет найдена искомая повторяющаяся последовательность, либо дробная часть закончится.

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

Измеряем время работы программы

Для измерения времени работы программы подключаем библиотеку time.h:

  • #include <time.h> 

С помощью функции clock() измеряем время начала (begin) и окончания (end) работы программы. В итоге время работы будем находить как разность между ними.

* Примечания:

clock_t это тип данных, определенный через typedef. По сути это обычный целочисленный long

(double) приводит число (в данном случае – результат вычислений end – begin) к вещественному типу данных double (числа с точкой)

CLOCKS_PER_SEC – это константа, сохраненная в библиотеке и зависящая от типа устройства, на котором запускается программа. В данном случае – это просто 1000

Функции, использующиеся в программе (проект Эйлера - 26 задача)

Функция, которая возвращает длину повторяющейся последовательности - проект Эйлера (26 задача)
Функция, которая возвращает длину повторяющейся последовательности

Функция, которая возвращает длину повторяющейся последовательности

Порядок работы функции

Функция работает на вышеописанном алгоритме:

  • При необходимости увеличивает числитель дроби на необходимое количество порядков.
  • Вычисляет остаток от деления дроби.
  • Заносит остаток в массив.
  • Принимает остаток за числитель дроби и в итоге повторяет цикл.

Работа цикла while  останавливается, если на одной из итераций остаток от деления становится равен нулю. Это будет означать, что дробная часть закончилась.

Также цикл остановится, когда встретится остаток от деления, который ранее уже был. В таком случае – обнаружена повторяющаяся последовательность.

Второе улучшение программы

Как сохранять вычисленные остатки? 

В данной функции используются два массива.

В одном (res_cash[]) значения будут сохраняться по их индексу. 

  • res_cash[8] = 1; // сохраняем число 8  в массиве (был такой остаток);
  • res_cash[7] = 0; // числа 7 (остатка) не было еще.

В итоге это позволяет, как легко сохранять значения остатков, так и проверять, не вычислялся ли такой остаток ранее.

Во второй массив (res_arr[]) значения остатков охраняются по порядку, в котором они вычисляются. Необходимость этого будет подробно объяснено ниже.

Помимо сохранения чисел по их индексу, в программе применены динамические массивы. Сделано так потому, что иначе (при использовании массивов фиксированной длины) будет перерасход памяти и лишние операции при обработке излишне длинных массивов. Нет необходимости при обработке дроби 1 / 8  использовать 1000-значный массив.

Поскольку мой компилятор не поддерживает массивы переменной длины типа:

  • int len = 100;
  • arr[len];

… потому воспользовался функциями calloc() и free():

  • calloc() – выделяет память необходимого размера и сразу же заполняет ее нулями;
  • free() – освобождает память после ее использования (это обязательно!)

Функция, которая возвращает индекс первого включения остатка в массив

Функция, которая возвращает индекс первого включения остатка в массив - проект Эйлера (26 задача)
Функция, которая возвращает индекс первого включения остатка в массив

Функция принимает массив (с занесенными в него остатками), число и ищет в нем индекс первой ячейки с  искомым значением.

Зачем это нужно? Поясню на примере.

Если вычислять значение дроби 1/6, то мы в итоге получим значение 0.166666... Мы видим, что цифра 6 в дробной части бесконечно повторяется. Однако при этом видно, что повторяющийся цикл не начинается с первого же знака после запятой.

Вычисления останавливаются, когда остаток совпал с ранее уже вычисленным. Потому, чтобы вычислить искомую длину цикла нам и понадобится индекс этого (ранее вычисленного) остатка. Длина цикла в итоге будет равна разности между их индексами.

Ответ и скорость работы программы (проект Эйлера - 26 задача)

Проект Эйлера (26 задача) - ответ
26 задача ответ

В итоге ответ на 26 задачу проекта Эйлера составил 983, программа считает ответ практически мгновенно – за 3 миллисекунды. 

По традиции хотелось бы выразить глубочайшую признательность Сергею Балакиреву за его титанический труд по созданию бесплатных курсов и в частности, за “Язык программирования C/C++ для начинающих“.

проект Эйлера
Курс "C/C++ для начинающих"

Есть вопросы, господа? Отвечаем спокойно, четко и только по делу))

Есть вопросы, господа?

Проект Эйлера (ответы) | © 2023-2024 | eulerproject.ru

Проект Эйлера (ответы) | © 2023-2024 | eulerproject.ru

© 2023-2024 | eulerproject.ru