Flat Preloader Icon

Project Euler (answers)

Проект Эйлера: Ответ и решение 27 задачи (Квадратичные простые числа)

Проект Эйлера (27 задача) решается двумя циклами for() и одной функцией. Однако если уж делать программу, то красиво.

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

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

Эйлер опубликовал свою замечательную квадратичную формулу :
  • n^2 + n + 41
Оказалось, что согласно данной формуле можно получить 40 простых чисел, последовательно подставляя значения 0≤n≤39
 Однако, при n = 40,
  • 40^2 + 40 + 41 = 40(40 + 1) + 41
делится на 41 без остатка.
И, очевидно, при n = 41,
  • 41^2 + 41 + 41
делится на 41 без остатка.
При помощи компьютеров была найдена невероятная формула
  • n^2−79*n + 1601
 согласно которой можно получить 80 простых чисел для последовательных значений n от 0 до 79. Произведение коэффициентов  −79 и 1601 равно −126479.
Рассмотрим квадратичную формулу вида :
  • n^2 + a*n + b
 где |a| < 1000    и |b| ≤1000,  где |n| является модулем(абсолютным значением) n.

К примеру, |11|= 11 и |−4| = 4

Найдите произведение коэффициентов a и b квадратичного выражения, согласно которому можно получить максимальное количество простых чисел для последовательных значений n, начиная со значения n = 0

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

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

Проект Эйлера (27 задача) - решение часть 1
Проект Эйлера (27 задача) - решение, часть 1

Решил не мельчить и потому вынес объявление функций и подключение заголовочных файлов на отдельный скриншот. 

Проект Эйлера (27 задача) - решение, часть 2
Проект Эйлера (27 задача) - решение, часть 2

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

Если просто следовать заданию, то необходимо:

  • Перебирать коэффициенты a и  b в циклах.
  • Подставлять их в формулу n^2 + a*n + b.
  • Изменяя в формуле n, вычислять ее значения одно за другим.
  • Каждое вычисленное значение, необходимо проверить, не является ли оно простым.
  • Определить в итоге коэффициенты a и b, с которыми формула дает максимальное количество простых чисел.

Улучшаем программу

Изменяя коэффициенты от -1000 до 1000, мы получим 2000 значений a и 2000 значений b и потому четыре миллиона их сочетаний. Однако и значений коэффициента n будут десятки. В итоге формула вернет десятки и сотни миллионов значений, каждые из которых понадобится проверить на то, не являются ли они простыми числами.

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

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

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

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

  • #include <time.h> 

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

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

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

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

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

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

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

Функция, которая увеличивает размер массива

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

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

Далее, со старого массива информация переносится на новый и старый массив удаляется. Изменяется и значение переменной len_arr, хранящей длину массива.

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

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

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

Как сохраняем числа

Составное число – это число не являющееся простым. Т.е. то, которое делится на какое-либо число, кроме единицы и себя самого.

Числа в массиве будут сохраняться по индексу следующим образом:

  • массив[число] = 0; // простое число
  • массив[число] = 1; // составное число

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

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

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

Далее функция работает точно также, как если бы мы определяли простое число. Однако есть отличие.

Поскольку в программе будет использоваться “решето Эратосфена”, потому за единицу удобнее принимать именно составное число.

Отличия от “наивного” алгоритма:

  • Поиск делителя числа производится лишь до корня квадратного из него. Если до этого момента он не найден, то и дальше нет смысла проверять.
  • Функция sqrt(), вычисляющая квадратный корень, вынесена перед циклом for(). Сделано так, потому что это позволяет избежать вычисления корня на каждой итерации.
  •  Функция принимает массив с вычисленными ранее составными и простыми числами и проверяет как делители только простые.

Функция, которая определяет превышение переменной своего предельного значения и выводит предупреждение

Функция, которая определяет превышение переменной своего предельного значения и выводит предупреждение
Функция, которая определяет превышение переменной своего предельного значения и выводит предупреждение

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

Для данной проверки и придумана функция input_mote_limit(). Если функция принимает не те значения, на которые она рассчитана, она прекратит ее работу (вернет true – должно дальше обрабатываться) и выведет предупреждение.

Пример вывода предупреждения
Пример вывода предупреждения

Функция, которая отмечает составные числа в массиве

Функция, которая отмечает составные числа в массиве
Функция, которая отмечает составные числа в массиве

Данная функция отмечает составные числа в массиве по принципу “решета Эратосфена”.

Поясню на примере. Начинаем проверять ряд чисел:

  • 2 – простое число;
  • Отмечаем в массиве числа кратные 2 как составные: 4, 6, 8, 10
  • 3 – простое число;
  • Таким же образом отмечаем числа кратные 3: 6, 9, 12, 15
  • 4 – пропускаем, оно уже отмечено как составное.
  • И так далее.

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

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

Например, простое число (prime) задается как 3 и начало заполнения массива определено с 13 (start). В этом случае первым составным числом, кратным 3, будет 15. С него в итоге и начнется заполнение массива.

Также функция проверяет корректность ввода всех переменных.

Функция, которая добавляет в массив ряд составных чисел

Функция, которая добавляет в массив ряд составных чисел
Функция, которая добавляет в массив ряд составных чисел

В отличие от предыдущей эта функция – более сложная. Она не просто отмечает составные числа кратные простому. Она расширяет массив с уже вычисленными простыми и составными числами, новыми числами.

Подразумевается, что в массиве comp_arr[], от индекса 2 до индекса start уже определен  статус чисел (простое / составное). Потому на первом этапе функция проходит от 2 до start, и найдя простые числа там, отмечает кратные им в добавляемом диапазоне от start до finish.

На втором этапе собственно уже и проходится добавляемый диапазон. Числа проверяются и найдя простые, отмечаются им кратные.

Функция, которая считает количество простых чисел подряд

Функция, которая считает количество простых чисел подряд
Функция, которая считает количество простых чисел подряд

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

  • Она принимает коэффициенты a и b.
  • Перебирая значение n, производится вычисление заданной формулы.
  • Вычисленное значение проверяется в массиве, не является ли оно простым.
  • Однако, если вычисленное значение выходит за пределы массива, то его размер увеличивается и статус дополнительных чисел определяется.
  • Количество результатов вычислений, являющихся простым числом подсчитывается.
  • В итоге как только один из результатов окажется составным числом, работа функции заканчивается и возвращается результат.

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

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

В итоге ответ на 27 задачу проекта Эйлера составил -59231, программа считает ответ быстро – за 20 миллисекунд. 

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

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

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

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

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

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

© 2023-2024 | eulerproject.ru