К содержанию

Подтема 2 из 7

Жадный выбор

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

Больше всего файлов

На диске свободно 60, а размеры файлов — 8, 12, 15, 19, 22 и 34. Как сохранить как можно больше файлов?

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

Файлы 8, 12, 15 и 19 занимают 54 из 60; следующий, 22, уже не помещается: 54 + 22 = 76 > 60. Файл 34 ещё больше.

Получили четыре файла. Почему пять сохранить невозможно? Потому что пять самых маленьких файлов имеют сумму 8 + 12 + 15 + 19 + 22 = 76 > 60, а любые другие пять файлов имеют не меньшую сумму. Это видно, если выписать оба набора по возрастанию друг под другом: i-й файл любого набора не меньше i-го самого маленького. На рисунке то же сравнение для наборов из четырёх файлов.

Четыре самых маленьких и другой набор из четырёх, оба по возрастанию: каждый файл набора не меньше файла на той же позиции сверху, поэтому и сумма не меньше: 79 ≥ 54.
Обоснование · необязательноДоказательство для любых размеров

Пусть отсортированные размеры a₁ ≤ a₂ ≤ … ≤ aₙ. У любого выбранного набора из k файлов после его собственной сортировки b₁ ≤ b₂ ≤ … ≤ bₖ. Обязательно b₁ ≥ a₁, b₂ ≥ a₂, …, bₖ ≥ aₖ: среди всех файлов есть по крайней мере i файлов, не больших bᵢ, — это b₁, …, bᵢ, — а aᵢ — i-е наименьшее значение, поэтому bᵢ ≥ aᵢ. Следовательно, b₁ + … + bₖ ≥ a₁ + … + aₖ. То есть первые k элементов имеют минимальную возможную сумму среди всех наборов из k элементов: если не помещаются они, не поместится никакой набор из k.

Код повторяет ручные действия. Пройди его по шагам:

sizes = [19, 8, 34, 12, 22, 15]capacity = 60sizes.sort()used = 0count = 0for size in sizes:    if used + size > capacity:        break    used += size    count += 1print(count, used)
4 54

Здесь used — уже занятый объём, count — количество выбранных файлов. Проверяем возможность добавления до изменения суммы.

Почему разрешён ? Потому что список отсортирован по возрастанию: если текущий файл не помещается, следующие ещё крупнее. Без сортировки такой выход из цикла мог бы пропустить маленький файл в конце списка: для [19, 8, 34, ...] цикл остановился бы на 34, не дойдя до 12 и 15.

Жадный алгоритм

Брать самое маленькое — это и есть «жадный алгоритм»? И всегда ли он прав?

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

Правило «из оставшихся файлов берём самый маленький» — жадное. Слово «жадный» не означает «всегда берёт самое большое»: он может выбирать самый маленький размер, самое раннее окончание, наибольшую допустимую коробку, самый дешёвый подходящий объект.

Одного ощущения «так выгоднее» мало. Объясняют двумя способами. Первый: выбранный объект оставляет не меньше возможностей. Файл поменьше оставляет больше места для следующих. Второй: можно заменить чужой выбор нашим и не ухудшить результат. Если в некотором лучшем наборе взят большой файл, а меньший не взят, их можно поменять: количество сохранится, занятое место не увеличится. Такое рассуждение называют доказательством обменом.

Важно учитывать всю формулировку. При вместимости 10 и размерах 4, 5, 6 лучший набор зависит от цели — выбери цель:

больше всего объектов
4 + 5два объекта, самые маленькие
наибольшая сумма
4 + 6 = 10самые маленькие дают только 9
наибольший отдельный объект
6один файл помещается целиком

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

Самый большой файл

«При максимальном количестве файлов укажите наибольший файл, который может быть сохранён». Это просто последний из выбранных?

Нет. Закрепим k − 1 самых маленьких файлов и найдём наибольший файл, который влезает в оставшееся место.

Максимальное количество равно четырём, и первоначально выбраны 8, 12, 15, 19. Но наибольший файл, который может оказаться среди четырёх сохранённых, — 22: набор 8, 12, 15, 22 занимает 57 ≤ 60.

Как получить это системно? Зафиксируем три самых маленьких файла — 8, 12, 15. Они занимают 35, для четвёртого остаётся 60 − 35 = 25. Из оставшихся файлов выбираем максимальный размер, не превышающий 25. Это 22.

Три самых маленьких закреплены, свободно 25: файлы 19 и 22 помещаются, 34 — нет. Наибольший помещающийся — 22.

Почему можно зафиксировать именно k − 1 самых маленьких? Потому что они оставляют максимально возможное место для ещё одного файла. Если некоторый файл x нельзя разместить даже вместе с k − 1 самыми маленькими, то с любыми другими k − 1 файлами он тем более не поместится.

Полная программа для формата S N в первой строке:

with open("26.txt") as f:    capacity, n = map(int, f.readline().split())    a = [int(f.readline()) for i in range(n)] a.sort()used = 0count = 0for size in a:    if used + size > capacity:        break    used += size    count += 1 fixed_sum = used - a[count - 1]limit = capacity - fixed_sumlargest = a[count - 1]for size in a[count - 1:]:    if size > limit:        break    largest = size print(count, largest)
4 22

Вторая половина программы — это рисунок выше: закрепили count − 1 самых маленьких и ищем наибольший файл, который помещается в оставшееся место limit.

Подробный разбор · необязательноРазбор второй половины программы

used — сумма первых count файлов, a[count - 1] — последний из них; вычитая его, получаем сумму первых count − 1 файлов. Перебор начинается с , поэтому закреплённые первые count − 1 файлов не используются повторно, а break снова держится на сортировке. Программа предполагает, что хотя бы один файл помещается; что выводить, если не помещается ни один, говорит условие.

Этот приём максимизирует один файл. Он не является способом максимизировать общую сумму выбранного набора.

Обязательные первыми

«Важные файлы перенести в первую очередь, а на оставшееся место — как можно больше остальных». Что меняется?

Обязательные объекты кладут сразу, без выбора, а жадный выбор идёт по оставшемуся месту среди остальных. Пусть файл 34 обязателен. В записи второе поле — 1 у обязательного:

capacity = 60files = [(8, 0), (12, 0), (15, 0), (19, 0), (22, 0), (34, 1)]used = sum(size for size, must in files if must)count = sum(must for size, must in files)for size in sorted(s for s, must in files if not must):    if used + size > capacity:        break    used += size    count += 1print(count, capacity - used)
3 6

34 занимает своё место сразу, свободно 26: туда входят 8 и 12, а 15 уже нет. Всего три файла, свободно 6. Так же решаются задачи о грузовике, где «грузы массой от 310 до 320 кг грузят в первую очередь»: обязательность определяет условие, а не размер.

Покупка партиями

В строке файла не одно изделие, а партия: цена и количество. Как купить больше всего изделий?

Идти по партиям от дешёвых и брать из каждой сколько хватит денег: всю партию или money // price изделий.

Пусть на 500 рублей нужно купить как можно больше изделий A, а на остаток — как можно больше изделий B. Партии A: 5 штук по 20 и 10 штук по 35; партии B: 4 штуки по 25 и 30 штук по 15.

Для каждого типа идём по партиям от дешёвых к дорогим и из партии берём min(q, money // p) изделий: либо всю партию, либо сколько хватит денег. Деление нацело отвечает на вопрос «сколько раз цена помещается в остаток».

money = 500lots = {"A": [(20, 5), (35, 10)], "B": [(25, 4), (15, 30)]}bought = {}for kind in "AB":    count = 0    for price, q in sorted(lots[kind]):        take = min(q, money // price)        count += take        money -= take * price        if take < q:            break    bought[kind] = countprint(bought["A"], bought["B"], money)
15 3 5

Все изделия A стоят 5 · 20 + 10 · 35 = 450, на остаток 50 покупаем 3 изделия B по 15 и остаётся 5. Почему после неполной партии можно остановиться? Денег не хватило даже на изделие этой партии, а следующие партии не дешевле.

Покупки двух типов

Купить больше всего изделий, а среди таких покупок — больше изделий типа A. Как учесть второй критерий?

Количество находим как раньше — самыми дешёвыми. Потом перебираем, сколько из них будет изделий A: для каждого числа самая дешёвая покупка однозначна.

В подборке есть задачи с тремя приоритетами: купить максимальное общее количество изделий, затем максимальное количество изделий типа A, затем потратить минимальную сумму. Возьмём бюджет 36, цены изделий A — 4, 9, 14, изделий B — 3, 5, 6, 10.

Сначала определим максимальное общее количество. Все цены по возрастанию: 3, 4, 5, 6, 9, 10, 14. Пять самых дешёвых стоят 27, шесть — уже 37 > 36. Значит, нужно купить ровно пять изделий — это рассуждение прошлого шага.

Теперь спрашиваем: сколько из этих пяти может быть изделий A? Если берём x изделий A, изделий B должно быть 5 − x. Для фиксированного x самая дешёвая покупка состоит из x самых дешёвых A и 5 − x самых дешёвых B.

Значит, достаточно быстро находить суммы первых нескольких элементов. Для этого строят накопленные суммы, их также называют префиксными суммами: «префикс» — начальный участок последовательности. Для A = 4, 9, 14 получим PA = 0, 4, 13, 27: PA[0] — стоимость нуля изделий, PA[1] — одного самого дешёвого, PA[2] — двух, PA[3] — трёх. Каждая следующая накопленная сумма равна предыдущей плюс очередная цена. Для B: PB = 0, 3, 8, 14, 24.

Количество AКоличество BМинимальная стоимостьПодходит?
144 + 24 = 28Да
2313 + 14 = 27Да
3227 + 8 = 35Да

Выбираем три A и два B. Остаток равен 1.

budget = 36a = sorted([4, 9, 14])b = sorted([3, 5, 6, 10])count = spent = 0for price in sorted(a + b):    if spent + price > budget:        break    spent += price    count += 1 prefix_a = [0]for price in a:    prefix_a.append(prefix_a[-1] + price)prefix_b = [0]for price in b:    prefix_b.append(prefix_b[-1] + price) best_a = best_cost = 0for count_a in range(count + 1):    count_b = count - count_a    if count_a > len(a) or count_b > len(b):        continue    cost = prefix_a[count_a] + prefix_b[count_b]    if cost <= budget:        best_a = count_a        best_cost = costprint(best_a, budget - best_cost)
3 1

Здесь нет перебора всех наборов покупок: перебирается только количество изделий A, а лучший набор с таким количеством строится однозначно.

Обоснование · необязательноПочему программа выполняет все три условия

Первое — количество: count найден набором самых дешёвых, как в прошлом шаге. Второе — больше A: count_a перебирается по возрастанию, поэтому ответ можно просто перезаписывать — каждый следующий подходящий вариант содержит больше A. Третье — дешевле: для каждого состава мы уже берём самые дешёвые изделия обоих типов. пропускает составы, для которых изделий одного из типов не хватает.

Скидки

«Каждый третий товар бесплатно»: какой именно товар окажется бесплатным?

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

Одна фраза об акции ещё не определяет алгоритм. Выбери правило:

можно выбрать любые k товаров со скидкой
выгодно выбирать наиболее дорогие подходящие
в каждой паре скидка на дешёвый товар
нужно выгодно составить пары
в каждой тройке бесплатно самый дешёвый
нужно выгодно составить тройки
продавец сам переставляет товары
нужно учитывать цель продавца
скидка действует только выше порога
сначала выделяются подходящие товары

Рассмотрим конкретное правило:

Все товары нужно купить. Их можно разбить на тройки. В каждой полной тройке самый дешёвый товар бесплатный.

Цены: 100, 90, 80, 70, 60, 50, 40. Выгодные тройки — 100, 90, 80 и 70, 60, 50: бесплатно получаем 80 и 50, а 40 покупаем отдельно. Сумма всех цен 490, стоимость покупки 490 − 80 − 50 = 360. После сортировки по убыванию бесплатны элементы с индексами 2, 5, 8, …; третий параметр — шаг:

prices = [70, 100, 40, 90, 50, 80, 60]prices.sort(reverse=True)free_sum = 0for i in range(2, len(prices), 3):    free_sum += prices[i]print(sum(prices) - free_sum)
360

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

Обоснование · необязательноПочему соседние тройки оптимальны

Чтобы получить один бесплатный товар некоторой цены, к нему нужны ещё два товара не дешевле. Значит, самый дорогой бесплатный товар не может быть дороже третьего по цене, второй по дороговизне бесплатный — не дороже шестого, третий — не дороже девятого. Разбиение отсортированного списка на соседние тройки достигает этих границ.

Правило может работать и в пользу покупателя: если в каждой тройке бесплатен самый дорогой товар, к каждому бесплатному нужны два более дешёвых спутника. Тогда бесплатными выгодно сделать ⌊n / 3⌋ самых дорогих товаров, а спутников взять из дешёвых: для тех же цен бесплатны 100 и 90.

В подборках встречается и «каждый четвёртый товар в чеке за полцены». Покупатель ставит на 4-е, 8-е, … места самые дорогие товары, а касса, которая сортирует цены себе в пользу, — самые дешёвые. Для цен 100, 90, 80, 70, 60, 50, 40, 30 за полцены идут два товара: у покупателя 100 и 90 — он платит 425, у кассы 30 и 40 — 485.

prices = [60, 100, 30, 80, 50, 90, 40, 70]prices.sort(reverse=True)q = len(prices) // 4buyer = sum(prices) - sum(prices[:q]) // 2shop = sum(prices) - sum(prices[-q:]) // 2print(buyer, shop)
425 485

Если товары переставляет продавец и хочет дать скидку поменьше, скидка достаётся самым дешёвым из подходящих товаров: продавец ставит их на чётные места. Например, «скидка 25 % на каждый второй товар дороже 50 рублей»: из k таких товаров скидку получат ⌊k / 2⌋ самых дешёвых.

Скидка в процентах и округление вверх

Пусть скидка 25 % применяется к товарам с суммарной исходной ценой D, а сумма всех цен — T. Тогда точная итоговая стоимость T − D/4 = (4T − D)/4. Если всю итоговую сумму нужно округлить вверх, делают целочисленно: (x + d - 1) // d — это x/d, округлённое вверх, для неотрицательного целого x.

prices = [65, 300, 45, 120, 200, 80]big = sorted(p for p in prices if p > 50)discount = big[:len(big) // 2]total = sum(prices)numerator = 4 * total - sum(discount)print(discount, (numerator + 3) // 4)
[65, 80] 774

Товаров дороже 50 пять, скидку получают два самых дешёвых из них — 65 и 80. Точная стоимость 810 − 145/4 = 773,75, после округления вверх — 774. Нельзя округлять стоимость каждого товара отдельно, если условие требует округлить только общую сумму.

Итого

  • Больше всегоот самых маленьких, break после первого лишнего
  • Наибольший при kзакрепить k − 1 самых маленьких
  • Жадный выборобъяснить: оставляет не меньше возможностей
  • Обязательныесначала они, потом выбор по остатку
  • Партииmin(q, money // price)
  • Скидкипокупатель — дорогие, продавец — дешёвые

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1
Вместимость 15, файлы в порядке записи 9, 20, 3. Что выведет программа с break без сортировки и какой ответ верный?
ОтветСкрыть ответ
Ответ
Без сортировки — 1: цикл берёт 9 и останавливается на 20. Верно — 2: файлы 3 и 9.
Вопрос 2
Вместимость 60, файлы 30, 9, 14, 6, 21. Сколько файлов можно сохранить и какой наибольший файл может оказаться среди них?
ОтветСкрыть ответ
Ответ
4 и 30. По возрастанию: 6 + 9 + 14 + 21 = 50, с 30 уже 80. Три самых маленьких занимают 29, для четвёртого свободно 31 — влезает 30.
Вопрос 3
Почему для наибольшего файла закрепляют k − 1 самых маленьких, а не все k?
ОтветСкрыть ответ
Ответ
Последнее, k-е место отдают большому файлу. k − 1 самых маленьких оставляют ему наибольший возможный остаток.
Вопрос 4
Файл 34 сохранить обязательно, вместимость 60, остальные файлы 8, 12, 15, 19, 22. Сколько файлов окажется на диске?
ОтветСкрыть ответ
Ответ
3: после 34 свободно 26, туда входят 8 и 12 (20), а 15 уже нет.
Вопрос 5
«В каждой тройке бесплатен самый дешёвый товар». Цены 50, 40, 30, 20, 10. Сколько заплатить при самом выгодном разбиении на тройки?
ОтветСкрыть ответ
Ответ
120: тройка 50, 40, 30 даёт бесплатно 30, а 20 и 10 тройки не образуют. 150 − 30 = 120.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Вместимость 30

Вместимость 30, размеры 5, 7, 8, 9, 12, 16. Найди максимальное количество файлов и максимальный возможный размер одного файла при таком количестве.

Введите два числа через пробел в том порядке, в каком их спрашивают.

Вместимость 10

Вместимость 10, размеры 4, 5, 6. Найди максимальное количество файлов, максимальную сумму сохранённых файлов и наибольший файл среди наборов максимального количества.

Введите три числа через пробел в том порядке, в каком их спрашивают.

Архив на диске

В файле 26-3.txt в первой строке записаны свободный объём диска S и количество файлов N, в каждой следующей — размер одного файла (натуральное число, не больше 100). Определи максимальное количество файлов, которые можно сохранить на диске, и максимальный размер файла, который может быть сохранён при условии, что сохранено максимальное количество файлов.

26-3.txtОбъём диска, количество и размеры файловТекстовый файл · 14,3 КБ

Введите два числа через пробел в том порядке, в каком их спрашивают.

Обязательные файлы первыми

На диске свободно 100. Файлы (размер, обязательный ли): (30, да), (18, нет), (7, нет), (25, да), (12, нет), (40, нет), (9, нет), (22, нет). Обязательные файлы переносят в первую очередь, а на оставшееся место — как можно больше остальных. Сколько файлов перенесут и сколько места останется?

Введите два числа через пробел в том порядке, в каком их спрашивают.

Партии A, затем B

Есть 300 рублей. Партии изделий A: 4 штуки по 25 и 3 штуки по 40; партии изделий B: 6 штук по 10 и 5 штук по 30. Сначала покупают как можно больше A, на остаток — как можно больше B. Сколько куплено изделий B и сколько денег осталось?

Введите два числа через пробел в том порядке, в каком их спрашивают.

Больше всего, затем больше A

Бюджет 20, цены изделий A — 3, 8, 9, изделий B — 2, 4, 7. Нужно купить как можно больше изделий, среди таких способов — как можно больше A, среди них — самый дешёвый. Сколько изделий A купят и сколько денег останется?

Введите два числа через пробел в том порядке, в каком их спрашивают.

В файле 26-4.txt в первой строке записаны количество изделий у поставщика N и выделенная сумма M, в каждой следующей — цена изделия и его тип (латинская буква A или B). Нужно купить как можно больше изделий; если это можно сделать разными способами — купить как можно больше изделий A; если и так способов несколько — потратить как можно меньше. Запиши количество купленных изделий A, затем оставшуюся сумму.

26-4.txtЦены и типы изделийТекстовый файл · 6,5 КБ

Введите два числа через пробел в том порядке, в каком их спрашивают.

Каждый четвёртый за полцены

В магазине акция: каждый четвёртый товар в чеке — за полцены. Цены товаров: 120, 46, 300, 80, 16, 60, 210, 96, 40, 180, 70, 24. Покупатель раскладывает товары на ленте так, чтобы заплатить как можно меньше, а касса сортирует их так, чтобы стоимость была как можно больше. Сколько заплатил бы покупатель при своей раскладке и сколько возьмёт касса?

Введите два числа через пробел в том порядке, в каком их спрашивают.

Тройки с бесплатным товаром

Цены 120, 45, 300, 80, 60, 95, 210, 150. Товары делят на тройки, в каждой полной тройке один товар бесплатный. Сколько заплатит покупатель, если бесплатен самый дешёвый в тройке и тройки он составляет сам? А если бесплатен самый дорогой?

Введите два числа через пробел в том порядке, в каком их спрашивают.

Каждый второй дороже 100

Те же цены. Скидка 20 % — на каждый второй товар дороже 100 рублей; порядок товаров выбирает продавец так, чтобы скидка была наименьшей; итог округляется вверх до рубля. Найди итог и цену самого дорогого товара со скидкой.

Введите два числа через пробел в том порядке, в каком их спрашивают.

Самый дешёвый в тройке бесплатно

В файле 26-5.txt в первой строке записано количество товаров N, в каждой следующей — цена товара. В магазине акция: товары делят на тройки, и в каждой полной тройке самый дешёвый товар бесплатный; оставшиеся один-два товара оплачиваются полностью. Покупатель покупает все товары и делит их на тройки самым выгодным для себя способом. Запиши сумму, которую он заплатит, затем цену самого дорогого бесплатного товара.

26-5.txtЦены товаровТекстовый файл · 14,1 КБ

Введите два числа через пробел в том порядке, в каком их спрашивают.