Flat Preloader Icon

Project Euler (answers)

Проект Эйлера: Ответ и решение 31 задачи (Суммы монет)

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

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

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

В Англии валютой являются фунты стерлингов £ и пенсы 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, используя любое количество монет?

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

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

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

Поскольку функция main() получилась довольно-таки объемной, потому решил не “мельчить” и разбить ее на два скриншота.

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

Суть динамического алгоритма в программе

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

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

В данном случае мы посчитаем количество вариантов для каждой суммы монет от 1 до 200 пенни, для разных вариантов сочетаний монет. Звучит сложно, однако, это не так.

Например, сколько вариантов получить сумму в 50 пенни из монет номиналом 1 пенни? Один! Более того, какую бы сумму нам не было бы нужно получить, количество вариантов останется одним.

Для других сочетаний номиналов монет (1p + 2p, 1p + 2p 5p и т.д. смотрите задание) количество вариантов будет изменяться, однако вычисляться они будут:

  1. по одной формуле;
  2. и на основе уже вычисленных значений.

Заполняем массив начальными данными

Заносим coins_arr[] в массив все номиналы монет, из которых будет составляться сумма отсортированные  в порядке возрастания.

Для записи количества способов составить данную сумму из данного набора монет создаем массив sum_arr[ ][ ]. Он будет состоять из 8 подмассивов (количество монет) длиной 201 (максимальная сумма монет – 200). Поскольку в процессе написания программы менялись как номиналы монет, так и искомые суммы, потому размеры массива и константы в циклах сделал:

  • #define LEN_ARR 201 – вынес длину массива (зависит от искомой суммы монет) перед программой.
  • Конструкция sizeof(coins_arr) / sizeof(coins_arr[0]  вычисляет длину массива coins_arr[ ].

При внесении новых номиналов монет или изменения длины массива в начале программе, в итоге изменения происходят по всему коду.

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

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

выше, его сразу заполняем единицами (один вариант).

В последующих подмассивах будут добавляться номиналы монет:

  • sum_ar…r[0] -> 1p (один пенни);
  • sum_arr[1]  -> 1p + 2p (один и два пенни);
  • и  итоге sum_arr[7] -> 1p + 2p + 5p + 10p + 20p + 50p + 100p + 200p (все номиналы).

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

Заполняем таблицу результатами вручную

Возьмем для примера небольшую сумму монет (10) и начнем заполнять массив пока вручную.

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

  • sum_arr[0] = [1,1,1,1,1,1,1,1,1,1,1]

Посчитаем для монет номиналом в один и два пенни.

Сумму в 1 пенни можно получить единственным способом, потому пишем в подмассив 1.

Два пенни можно получить уже двумя способами: либо монетой в 2 пенни, либо двумя монетами в один пенни. В итоге пишем в ячейку 2.

Пропустим часть подсчетов.

Десять пенни можно получить следующими сочетаниями монет:

  • 5(2p) + 0(1p) – пять монет по два пенни
  • 4(2p) + 2(1p)
  • 3(2p) + 4(1p)
  • 2(2p) + 8(1p)
  • 0(2p) + 10(1p)

Полученные 5 вариантов заносим в подмассив:

  • sum_arr[1] = [1,1,1,2,2,3,3,4,4,5,5]

Заполняем таблицу результатами автоматически

Для заполнения следующего подмассива (с монетами 1p, 2p и 5p) уже не будем вручную подсчитывать все варианты, а порассуждаем.

Для получения 10 пенни мы можем взять:

  • 0(5p) + 10(1p + 2p) – ни одной монеты 5 и десять монет номиналом 1 и 2 пенни. Поскольку количество комбинаций монет номиналом 1 и 2 уже посчитано ранее (5), возьмем его оттуда.  В итоге имеем эти же пять комбинаций.
  • 1(5p) + 5(1p + 2p) – количество комбинаций для 5 монет 1p и 2p также известно (3). Потому берем его из массива и имеем еще 3 комбинации.
  • 2(5p) + 0(1p + 2p) – две монеты достоинством в 5 пенни также являются комбинацией, которая отражена в массиве. Обращаясь к нулевому индексу предыдущего массива (ноль монет достоинством 1 и 2 пенни), мы получим 1.

В итоге мы получим 5 + 3 + 1 = 8 возможных комбинаций для суммы в 10 пенни трех номиналов (1p, 2p, 5p). 

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

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

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

  • #include <time.h> 

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

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

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

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

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

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

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

В итоге ответ на 31 задачу проекта Эйлера составил 73682, программа считает ответ мгновенно. 

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

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

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

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

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

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

© 2023-2024 | eulerproject.ru