Подтема 3 из 7
Цепочки коробок и пары
Вложенные коробки снаружи внутрь и изнутри наружу, рейсы по двое, грузы и контейнеры, детали двух видов.
Цепочка снаружи
Коробки со сторонами 12, 18, 21, 27, 34, 40 и 45; одну можно вложить в другую, если её сторона меньше хотя бы на 6. Как вложить друг в друга как можно больше коробок?
Строить цепочку снаружи: в каждую коробку класть самую большую из тех, что в неё помещаются. Большая коробка внутри оставляет больше места для следующих.
Начнём снаружи. Самая большая коробка — 45. В неё можно поместить коробку размером не больше 45 − 6 = 39. Из подходящих выбираем самую большую — 34. Почему не 12? Потому что после выбора 12 внутри почти не останется возможностей; коробка 34 оставляет больше места для последующих вложений.
- Размеры по убыванию: 45, 40, 34, 27, 21, 18, 12. Начинаем снаружи — с самой большой коробки.
- Внешняя коробка — самая большая, 45.
- В 45 помещается размер не больше 45 − 6 = 39. 40 не подходит: разница с 45 меньше 6. Берём наибольший подходящий — 34.
- В 34 помещается размер не больше 34 − 6 = 28. Берём наибольший подходящий — 27.
- В 27 помещается размер не больше 27 − 6 = 21. Берём наибольший подходящий — 21.
- В 21 помещается размер не больше 21 − 6 = 15. 18 не подходит: разница с 21 меньше 6. Берём наибольший подходящий — 12.
- Больше вложить нечего: 5 коробок, самая маленькая — 12.
Получилась цепочка 45 → 34 → 27 → 21 → 12 — пять коробок. Код: размеры по убыванию, коробка берётся, если она меньше последней выбранной хотя бы на зазор. Самая маленькая коробка — 12: это ответ на частый второй вопрос «наибольший размер самой маленькой коробки».
sizes = [12, 18, 21, 27, 34, 40, 45]gap = 6sizes.sort(reverse=True)selected = []for size in sizes: if not selected or selected[-1] - size >= gap: selected.append(size)print(len(selected), selected[-1])Здесь предполагается, что исходный список непустой и gap > 0. Условие истинно, пока цепочка пуста: первая, самая большая коробка берётся без проверки. Благодаря короткому вычислению or к selected[-1] пустого списка программа не обращается.
Обоснование · необязательноПочему жадная цепочка самая длинная
На каждой глубине жадная цепочка держит самую большую возможную коробку, поэтому всё, что помещается в чужую цепочку, поместится и в неё.
Пусть жадная цепочка g₁ > g₂ > …, а другая допустимая цепочка c₁ > c₂ > … > cₘ. Покажем по шагам, что gᵢ ≥ cᵢ для каждого i ≤ m. Для i = 1 это верно: g₁ — самая большая коробка вообще. Пусть gᵢ ≥ cᵢ. Коробка cᵢ₊₁ ≤ cᵢ − 6 ≤ gᵢ − 6, значит, она подходит для вложения в gᵢ, и жадный алгоритм найдёт подходящую коробку; он берёт наибольшую из подходящих, поэтому gᵢ₊₁ ≥ cᵢ₊₁. Следовательно, жадная цепочка не короче любой другой, а её i-я коробка не меньше i-й коробки любой другой цепочки — в том числе последняя.
Изнутри наружу
А если второй вопрос — «наименьший размер внешней коробки»?
Строить цепочку изнутри наружу: начать с самой маленькой и каждый раз брать самую маленькую коробку, в которую влезает предыдущая. Строй с той стороны, которую оптимизирует второй критерий.
Рассуждение то же, только зеркальное: маленькая внешняя коробка на каждом шаге оставляет больше выбора для следующей.
sizes = [12, 18, 21, 27, 34, 40, 45]gap = 6chain = []for size in sorted(sizes): if not chain or size - chain[-1] >= gap: chain.append(size)print(*chain)Пять коробок, внешняя имеет размер 40, а снаружи внутрь внешней была 45. Количество одинаковое, а второе число зависит от направления: строй от той стороны, которую оптимизирует второй критерий.
Этот разбор предполагает одинаковый минимальный зазор между соседними коробками и отсутствие других ограничений. Материалы и цвета, которые должны чередоваться, или зазор, растущий с номером коробки, — это усложнения из дополнительного урока: для них нужно новое обоснование.
Два указателя
В лодку садятся не больше двух человек, суммарная масса не больше вместимости. Как перевезти всех за наименьшее число рейсов?
Отсортировать массы и каждый раз сажать самого тяжёлого из оставшихся — вместе с самым лёгким, если они помещаются вдвоём. Два конца списка отмечают два указателя.
Указатель здесь — обычная переменная, хранящая индекс текущего элемента: left = 0 стоит в начале списка, right = len(a) - 1 — в конце.
Предполагается, что любой человек может ехать один. Если самый тяжёлый не помещается даже с самым лёгким, ни с кем другим он тоже не поместится — значит, едет один.
- Массы по возрастанию. left — самый лёгкий из оставшихся, right — самый тяжёлый.
- 50 + 130 = 180 > 150: с самым лёгким не помещается — значит, ни с кем. 130 едет один.
- 50 + 100 = 150 ≤ 150: самый тяжёлый едет с самым лёгким, оба указателя сдвигаются.
- 60 + 80 = 140 ≤ 150: самый тяжёлый едет с самым лёгким, оба указателя сдвигаются.
- Остался один человек, 70: он едет один.
- Всё: 4 поездки.
w = [80, 130, 50, 100, 70, 60]capacity = 150w.sort()left = 0right = len(w) - 1trips = 0while left <= right: if left < right and w[left] + w[right] <= capacity: left += 1 right -= 1 trips += 1print(trips)При совместной поездке двигаются оба индекса: использованы два человека. Если вместе не помещаются, используется только тяжёлый — двигается правый индекс. При left == right остался один человек, и условие left < right не позволяет использовать его дважды. Каждый указатель проходит список один раз, поэтому после сортировки работа — O(N).
Обоснование · необязательноПочему самого тяжёлого можно посадить с самым лёгким
Обоснование строится обменом: самого тяжёлого можно объединить с самым лёгким, не ухудшая необходимое количество пар. Если в другом размещении они ехали с другими людьми, пары можно перестроить: тяжёлый поедет с лёгким, а их бывшие партнёры — вместе. Оставшиеся партнёры совместимы, поскольку каждый из них не тяжелее самого тяжёлого, а тот помещался с лёгким. Число рейсов от перестройки не растёт.
Ограничение «не более двух» существенно. Для произвольного числа объектов в рейсе этот алгоритм уже не является решением общей задачи упаковки.
Грузы и контейнеры
Грузы и контейнеры даны двумя списками, в контейнер помещается один груз. Как отправить как можно больше грузов?
Идти по грузам от лёгких и каждому отдавать самый маленький из подходящих контейнеров. Большие контейнеры остаются тяжёлым грузам.
Иногда в строке файла два числа, которые не связаны между собой: масса груза и вместимость контейнера, причём каждый контейнер берёт один груз любой массы, не большей его вместимости. Это то самое исключение из правила «сортировать записи целиком»: грузы и контейнеры — два отдельных списка, и сортируют их по отдельности.
Пусть массы грузов 15, 40, 25, 48, 30, а вместимости контейнеров 20, 10, 50, 35, 45. Лёгкие грузы получают самые маленькие подходящие контейнеры: 15 → 20, 25 → 35, 30 → 45, 40 → 50, а для 48 контейнера уже нет. Отправлено 4 груза.
Почему так правильно? Самому лёгкому грузу подходит любой контейнер, подходящий другим; отдавая ему самый маленький, мы сохраняем большие контейнеры для тяжёлых грузов — снова «оставить больше возможностей».
Второй вопрос — наибольшая масса отправленного груза при максимальном количестве — решается приёмом из подтемы 2: закрепляем k − 1 = 3 самых лёгких груза в их самых маленьких контейнерах (20, 35, 45), а последний груз берём наибольший из тех, что помещаются в самый большой из оставшихся контейнеров — 50. Это 48, а не 40.
goods = sorted([15, 40, 25, 48, 30])boxes = sorted([20, 10, 50, 35, 45])count = j = 0for g in goods: while j < len(boxes) and boxes[j] < g: j += 1 if j == len(boxes): break count += 1 j += 1 used = [False] * len(boxes)j = 0for g in goods[:count - 1]: while boxes[j] < g: j += 1 used[j] = True j += 1room = max(b for b, u in zip(boxes, used) if not u)print(count, max(g for g in goods[count - 1:] if g <= room))Первый цикл считает количество, второй закрепляет k − 1 самых лёгких грузов: отмечает, какие контейнеры они заняли. Указатель j по контейнерам только растёт: контейнер, который мал для лёгкого груза, тем более мал для следующих, более тяжёлых.
Детали двух видов
Изделие собирают из детали A и детали B, размеры которых отличаются не больше чем на D. Как собрать больше изделий с наибольшей прибылью?
Отсортировать оба списка по убыванию и соединять крупнейшие совместимые детали; деталь, которой нет пары, пропускать.
Прибыль — сумма размеров, все размеры положительные. Сортируем оба списка по убыванию. Если самая большая A больше самой большой B более чем на D, эта A не подойдёт ни к одной оставшейся B — её можно пропустить. Аналогично для слишком большой B. Если крупнейшие совместимы — объединяем их.
Пусть A = 50, 42, 30, 12, B = 47, 33, 20, 18, D = 5. Пара 50 и 47 подходит; 42 больше 33 + 5 — деталь 42 пропускаем; 30 и 33 подходят; 20 больше 12 + 5 — пропускаем 20, затем и 18. Получаем 2 изделия с прибылью 97 + 63 = 160.
a = sorted([50, 42, 30, 12], reverse=True)b = sorted([47, 33, 20, 18], reverse=True)distance = 5i = j = 0count = profit = 0while i < len(a) and j < len(b): if a[i] > b[j] + distance: i += 1 elif b[j] > a[i] + distance: j += 1 else: count += 1 profit += a[i] + b[j] i += 1 j += 1print(count, profit)Обоснование · необязательноПочему можно объединить крупнейшие совместимые детали
Если в другом решении крупнейшие совместимые детали соединены с другими партнёрами, эти две пары можно перестроить: крупнейшие — вместе, их бывшие партнёры — вместе. Меньшие оставшиеся партнёры сохранят совместимость: их разность ограничивается одной из уже допустимых разностей. Если одна из крупнейших деталей не использовалась, замена меньшей детали на неё не уменьшает прибыль.
Здесь доказательство использует конкретную совместимость по разности размеров и конкретную формулу прибыли. При других правилах оно может перестать работать. Проверка перебором на тысячах маленьких наборов подтверждает: этот проход даёт и наибольшую прибыль, и наибольшее количество изделий.
Итого
- Снаружи внутрьв каждую — самую большую подходящую
- Изнутри наружуесли второй критерий — внешняя коробка
- Рейсы по двоетяжёлый с лёгким, два указателя
- Грузы и контейнерылёгкий груз — в самый маленький подходящий
- Детали A и Bкрупнейшие совместимые, лишнюю пропустить
- Обоснованиевыбор оставляет больше возможностей
Проверьте себя
Ответь сначала сам, потом открой ответ.
ОтветСкрыть ответ
4 5: 22, 13, 9, 5. Нет: разность 9 − 9 = 0 меньше 4.ОтветСкрыть ответ
ОтветСкрыть ответ
ОтветСкрыть ответ
ОтветСкрыть ответ
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Матрёшка с разницей ровно 1
Кубические коробки со сторонами 3, 5, 6, 7, 7, 9, 10, 11, 12, 15. Коробку можно вложить в другую, только если её сторона ровно на 1 меньше. Найди наибольшее число коробок в одной матрёшке и наибольшую сторону самой маленькой коробки в такой матрёшке.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Цепочка с разницей ровно 1 — это серия идущих подряд различных размеров.
Решение
Подходящие цепочки — серии подряд идущих размеров: 12, 11, 10, 9 и 7, 6, 5. Самая длинная — 4 коробки, самая маленькая в ней — 9.
Решение программой
sizes = [3, 5, 6, 7, 7, 9, 10, 11, 12, 15]best = (0, 0)chain = []for size in sorted(set(sizes), reverse=True): if chain and chain[-1] - size == 1: chain.append(size) else: chain = [size] best = max(best, (len(chain), chain[-1]))print(*best)Ответ: 4 9
Цепочка с зазором 6
Размеры коробок 10, 16, 21, 27, 34, 40, минимальная разность соседних коробок — 6. Найди максимальную длину цепочки и максимальный размер самой маленькой коробки при такой длине.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Снаружи внутрь: в каждую коробку — самую большую подходящую.
Решение
5 10: например, 40, 34, 27, 21, 10.
Решение программой
def chain(sizes, gap): sel = [] for x in sorted(sizes, reverse=True): if not sel or sel[-1] - x >= gap: sel.append(x) return sel sel = chain([10, 16, 21, 27, 34, 40], 6)print(len(sel), sel[-1])Ответ: 5 10
Матрёшка с зазором 7
В файле 26-6.txt в первой строке записано количество коробок N, в каждой следующей — длина стороны коробки (натуральное число, не больше 10 000). Одну коробку можно вложить в другую, если длина её стороны хотя бы на 7 меньше. Определи наибольшее количество коробок в одной «матрёшке» и максимально возможную длину стороны самой маленькой коробки в такой «матрёшке».
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Снаружи внутрь: самая большая подходящая коробка на каждой глубине.
Решение
1319 7: цепочка снаружи внутрь доходит почти до самой маленькой коробки.
Решение программой
with open('26-6.txt') as f: f.readline() a = [int(line) for line in f] def chain(sizes, gap): sel = [] for x in sorted(sizes, reverse=True): if not sel or sel[-1] - x >= gap: sel.append(x) return sel sel = chain(a, 7)print(len(sel), sel[-1])Ответ: 1319 7
Изнутри наружу
Те же коробки. Найди максимальную длину цепочки и минимальный размер самой большой коробки при такой длине.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Для наименьшей внешней коробки цепочку строят от самой маленькой.
Решение
5 40: изнутри наружу 10, 16, 27, 34, 40.
Решение программой
sel = []for x in sorted([10, 16, 21, 27, 34, 40]): if not sel or x - sel[-1] >= 6: sel.append(x)print(len(sel), sel[-1])Ответ: 5 40
Рейсы по двое
Массы людей 40, 55, 70, 85, 90, в рейсе не больше двух человек, вместимость 130. Сколько рейсов нужно?
Введите число.
Подсказка
Самый тяжёлый едет с самым лёгким, если они помещаются, иначе — один.
Решение
3: 90 + 40 = 130 вместе, 85 с 55 не помещается и едет один, затем 70 + 55 = 125.
Решение программой
a = sorted([40, 55, 70, 85, 90])left, right = 0, len(a) - 1trips = 0while left <= right: if left < right and a[left] + a[right] <= 130: left += 1 right -= 1 trips += 1print(trips)Ответ: 3
Грузы в контейнеры
Массы грузов 12, 18, 25, 27, вместимости контейнеров 15, 20, 28, 10; в контейнер помещается один груз. Сколько грузов можно отправить и какова наибольшая масса отправленного груза при таком количестве?
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Самый лёгкий груз — в самый маленький подходящий контейнер; затем замени последний груз.
Решение
3 27: 12 → 15, 18 → 20, 25 → 28; закрепив 12 и 18, в оставшийся контейнер 28 помещается 27.
Решение программой
g = sorted([12, 18, 25, 27])c = sorted([15, 20, 28, 10])used = [False] * len(c)j = count = 0for x in g: while j < len(c) and c[j] < x: j += 1 if j == len(c): break used[j] = True count += 1 j += 1used[max(i for i in range(len(c)) if used[i])] = Falseroom = max(c[i] for i in range(len(c)) if not used[i])print(count, max(x for x in g[count - 1:] if x <= room))Ответ: 3 27
Грузы и контейнеры из файла
В файле 26-7.txt в первой строке записано число N, в каждой следующей — два натуральных числа: масса очередного груза и вместимость очередного контейнера. Строка не связывает груз с контейнером: всего N грузов и N контейнеров. В контейнер помещается один груз, масса которого не больше вместимости контейнера. Определи максимальное количество грузов, которое можно отправить, и максимальную массу отправленного груза при условии, что отправлено максимальное количество грузов.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Грузы и контейнеры сортируются отдельно; последний груз — наибольший, помещающийся в оставшийся контейнер.
Решение
5641 8799: грузы и контейнеры сортируются отдельно; если сопоставлять груз с контейнером своей строки, ответ был бы другим.
Решение программой
with open('26-7.txt') as f: f.readline() data = [list(map(int, line.split())) for line in f] g = sorted(r[0] for r in data)c = sorted(r[1] for r in data)used = [False] * len(c)j = count = 0for x in g: while j < len(c) and c[j] < x: j += 1 if j == len(c): break count += 1 j += 1j = 0for x in g[:count - 1]: while c[j] < x: j += 1 used[j] = True j += 1room = max(c[i] for i in range(len(c)) if not used[i])print(count, max(x for x in g[count - 1:] if x <= room))Ответ: 5641 8799
Детали A и B
Детали A: 31, 44, 52, 60; детали B: 35, 47, 49, 70; разность размеров в изделии не больше 4. Сколько изделий получится и какова прибыль, если она равна сумме размеров деталей?
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
По убыванию: больший из двух несовместимых пропускается.
Решение
3 258: 60 и 70 пропускаются, пары 52 + 49, 44 + 47, 31 + 35.
Решение программой
a = sorted([31, 44, 52, 60], reverse=True)b = sorted([35, 47, 49, 70], reverse=True)i = j = count = profit = 0while i < len(a) and j < len(b): if a[i] > b[j] + 4: i += 1 elif b[j] > a[i] + 4: j += 1 else: count += 1 profit += a[i] + b[j] i += 1 j += 1print(count, profit)Ответ: 3 258
Заготовки A и B
В том же файле 26-7.txt первое число строки — размер заготовки A, второе — размер заготовки B. Изделие делают из одной заготовки A и одной B, размеры которых различаются не больше чем на 15; прибыль от изделия равна сумме размеров заготовок. Заготовки используют так, чтобы получить наибольшую прибыль. Запиши количество изделий, затем прибыль.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Два указателя по спискам, отсортированным по убыванию.
Решение
5514 54132404: два указателя по спискам, отсортированным по убыванию.
Решение программой
with open('26-7.txt') as f: f.readline() data = [list(map(int, line.split())) for line in f] a = sorted((r[0] for r in data), reverse=True)b = sorted((r[1] for r in data), reverse=True)i = j = count = profit = 0while i < len(a) and j < len(b): if a[i] > b[j] + 15: i += 1 elif b[j] > a[i] + 15: j += 1 else: count += 1 profit += a[i] + b[j] i += 1 j += 1print(count, profit)Ответ: 5514 54132404