Статья Автор: Деникина Н.В., Деникин А.В.

Вопрос 26. Паттерны

ЕГЭ по информатике · паттерны

Задание 26

Десять методов на одном каркасе — жадный набор, пары, контейнеры, порог через bisect, предпросчёт, события и вложенные структуры, — которые закрывают практически любую формулировку задания. 

2 первичных балларешения на Pythonуниверсальный каркас12 разделов23 задачи с даннымипроверенные ответы
00

Универсальный каркас

«Прочитать файл → (отсортировать) → пройти → вывести»

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

Чтение файла — три формата

data = list(map(int, open('26.txt').read().split()))

Когда в файле просто поток чисел без «шапки».

with open('26.txt') as f:
    n = int(f.readline())              # параметр в первой строке
    data = [int(f.readline()) for _ in range(n)]

Первая строка — лимит/количество, дальше по одному числу.

data = []
with open('26.txt') as f:
    for line in f:
        a, b = map(int, line.split())
        data.append((a, b))

Когда в каждой строке пара/тройка чисел (интервалы, цена+мощность).

Каркас решения

data = ...                 # 1. прочитать файл (см. вкладки выше)
data.sort()                # 2. отсортировать, если нужно

ans1 = ans2 = 0
for x in data:             # 3. один проход с накоплением / жадно
    ...

print(ans1, ans2)          # 4. вывести РОВНО то, что просят

Как выбрать паттерн по условию

  1. «максимальное количество … не превышая объём» → 01 жадный набор.
  2. «три наибольших», «k-е по величине» → 02 топ-k.
  3. «пары», «среднее/сумма пары» → 03 два указателя.
  4. «в порядке поступления», «новый контейнер» → 04 контейнеры.
  5. «минимальная вместимость/число, при которой» → 05 порог через bisect.
  6. «выручка», «каждый k-й бесплатно» → 06 выгода с группировкой.
  7. очень много запросов, но значения ограничены → 07 предпросчёт.
  8. события «начало–конец», максимум непересекающихся → 08; склеить пересекающиеся → 09.
  9. вложение с порогом разницы (коробки/матрёшка) → 10.
  10. «места по убыванию … при равенстве — по номеру», «кто на K-м месте» → 11 многокритериальная сортировка.

Три ошибки, общие для всех типов

  • Структура файла. Сначала открой файл и посмотри: есть ли «шапка», что в строках. Большинство потерь — здесь, а не в алгоритме.
  • Границы и знаки. [A; B] → range(A, B + 1); «не менее» это >=, «больше» — >.
  • Два ответа. В задании 26 почти всегда два числа — выведи оба и не перепутай, какое к вопросу А, какое к Б.
01

Жадный набор под ограничение

«Что влезет на флешку / в рюкзак / в архив»

Признаки в условии

  • «максимальное количество» предметов
  • ограничение на суммарный объём / вес / размер

Идея

Чтобы влезло как можно больше предметов, берём самые маленькие: сортируем по возрастанию и набираем, пока хватает места. Для второго вопроса важная тонкость: число файлов уже зафиксировано, поэтому самый большой из взятых можно заменить на более крупный — лишь бы он уместился в объём, оставшийся от count − 1 самых маленьких.

Условие

На носитель объёмом V требуется записать как можно больше файлов из N имеющихся (размер каждого задан). Найдите максимальное число файлов и наибольший возможный размер файла среди записанных (число файлов при этом остаётся максимальным).По мотивам задачи об архиве файлов.

Шаблон решения

with open('26.txt') as f:
    V = int(f.readline())          # вместимость
    n = int(f.readline())          # количество файлов
    sizes = [int(f.readline()) for _ in range(n)]

sizes.sort()                       # по возрастанию

# А. Максимум файлов: жадно набираем самые маленькие
total = 0
count = 0
for s in sizes:
    if total + s <= V:
        total += s
        count += 1
    else:
        break

# Б. Число файлов НЕ меняем, но самый большой из взятых
#    заменяем на как можно больший файл. Оставляем count-1
#    самых маленьких; на последний остаётся свободный бюджет:
budget = V - (total - sizes[count - 1])
best = 0
for s in sizes:
    if s <= budget:                # sizes отсортирован ->
        best = s                   # последний подходящий и есть максимум

print(count)                       # А: сколько файлов
print(best)                        # Б: наибольший возможный размер

Ловушки

  • Сравнивай total + s <= V с суммой, а не каждый элемент по отдельности.
  • Второй ответ — это не max взятых файлов. Число файлов фиксировано, поэтому самый большой из них можно заменить на более крупный, влезающий в остаток объёма: budget = V − (total − sizes[count−1]), а ответ Б — наибольший файл не больше этого бюджета.

Тренировка

Задача 1 — флешка

Первая строка — объём флешки V, вторая — число файлов N, далее N строк с размерами. А. Максимальное число файлов. Б. Наибольший возможный размер файла среди записанных (число файлов остаётся максимальным).

Данные (62 строк) — раскрыть и скопировать в файл
8000
60
732
382
416
379
458
435
551
86
432
745
290
867
778
264
263
281
277
441
158
641
217
94
800
827
868
498
501
598
359
878
872
250
312
548
361
584
747
184
621
728
746
832
565
870
713
705
732
451
101
289
520
84
278
525
514
586
171
396
626
259
Показать ответСкрыть ответ
Жадно набираем самые маленькие; затем самый большой заменяем на влезающий в остаток объёма. Эталон: А = 28    Б = 525 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — рюкзак

Первая строка — грузоподъёмность W, вторая — число предметов N, далее веса. А. Максимальное число предметов. Б. Оставшийся свободный вес.

Данные (52 строк) — раскрыть и скопировать в файл
5000
50
341
421
163
515
543
653
492
722
385
606
729
729
545
559
182
515
290
602
215
788
219
437
776
733
713
777
698
384
151
731
550
465
281
614
791
468
514
124
739
624
640
560
474
781
776
175
700
390
743
553
Показать ответСкрыть ответ
Жадно набираем, ответ Б = V − total. Эталон: А = 16    Б = 377 Напиши код по шаблону, запусти на этих данных и сверь.
02

Топ-k и k-е значение

«Три лучших результата», «сумма m наибольших»

Признаки в условии

  • «три наибольших», «k-е по величине»
  • важно — различных значений или нет

Идея

Сортируем (обычно по убыванию), берём порог — k-е по величине, считаем всё, что не меньше порога. Для «различных» сначала убираем повторы через set.

Условие

Дан список результатов участников. Определите, сколько из них показали один из трёх наибольших различных результатов, и сумму этих результатов.По мотивам задачи о результатах турнира.

Шаблон решения

nums = list(map(int, open('26.txt').read().split()))

top3 = sorted(set(nums), reverse=True)[:3]   # три наибольших РАЗЛИЧНЫХ
threshold = top3[-1]                          # порог: 3-е по величине
qualified = [x for x in nums if x >= threshold]

print(len(qualified))     # А: сколько участников
print(sum(qualified))     # Б: их суммарный результат

Ловушки

  • «Различных» (set) или «просто три наибольших» — при повторах ответы разные.
  • На границе считаем всех с пороговым значением — отсюда >=.

Тренировка

Задача 1 — три лучших

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

Данные (70 строк) — раскрыть и скопировать в файл
86
75
59
90
74
81
69
87
53
84
97
43
41
48
40
69
100
66
85
49
72
62
50
88
94
81
95
99
72
53
90
84
81
80
46
64
47
85
73
92
58
82
80
98
65
50
74
88
78
68
49
91
87
92
86
45
83
67
71
62
62
56
71
50
79
92
100
70
57
80
Показать ответСкрыть ответ
sorted(set(...))[:3], считаем ≥ порога. Эталон: А = 4    Б = 397 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — пять наибольших

Файл содержит числа. А. Сумма пяти наибольших значений. Б. Само пятое по величине значение.

Данные (55 строк) — раскрыть и скопировать в файл
6766
4299
1935
9314
5489
9871
8772
1533
2090
8765
5008
6038
9998
5551
8475
7016
6383
9442
6876
1247
7539
6795
4633
2322
5016
6991
1053
2054
771
4276
3511
1215
3668
2332
6214
1923
9595
6172
752
4689
1843
5363
1002
8213
8969
6960
3271
9481
6595
1807
6884
6045
3800
3892
5940
Показать ответСкрыть ответ
Сортируем по убыванию, берём 5 верхних. Эталон: А = 48387    Б = 9442 Напиши код по шаблону, запусти на этих данных и сверь.
03

Пары — метод двух указателей

«Объединить по двое по правилу»

Признаки в условии

  • слово «пара», «объединить по двое»
  • ограничение на сумму или среднее пары

Идея

Сортируем. Один указатель в начале (наименьший), другой в конце (наибольший). Соединяем крайние — так удаётся «пристроить» большой элемент к маленькому.

Условие

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

Шаблон решения

nums = list(map(int, open('26.txt').read().split()))
L = nums[0]                        # первый — порог суммы пары
nums = nums[1:]

nums.sort()
i, j = 0, len(nums) - 1
pairs = 0
while i < j:
    if nums[i] + nums[j] <= L:     # большой влез к маленькому
        pairs += 1
        i += 1
        j -= 1
    else:
        j -= 1
print(pairs)

Почему сортируем и почему крайние

Зачем сортировка. Чтобы видеть, кто «самый трудный» для пары: после сортировки слева — самый маленький элемент, справа — самый большой.

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

  • если даже с самым маленьким сумма больше L — этот большой не подходит никому, убираем его (j -= 1);
  • если подходит — соединяем в пару: маленький самый «уживчивый», его не жалко на трудного партнёра, а большой пристроен (i += 1; j -= 1).

На каждом шаге мы либо составляем пару, либо честно отбрасываем безнадёжный элемент — поэтому не теряем ни одной возможной пары. Это «обменное рассуждение»: любую лучшую расстановку можно без потерь свести к нашей.

Ловушки

  • Условие i < j (элемент не образует пару сам с собой).
  • «Среднее ≥ M» → сравнивай nums[i]+nums[j] >= 2*M (целые надёжнее дробей).

Тренировка

Задача 1 — пары стержней

Первая строка — предел суммы пары L, далее длины стержней. Максимальное число пар, суммарная длина которых не превышает L (каждый стержень — максимум в одной паре).

Данные (65 строк) — раскрыть и скопировать в файл
1500
750
698
417
835
980
612
324
385
910
542
616
792
311
1059
1162
785
940
897
882
555
888
200
789
1125
655
1030
1163
334
660
739
233
443
669
1097
1153
281
624
1147
442
286
469
395
616
292
706
1184
979
335
576
970
1117
1042
1192
442
819
1188
354
616
374
694
383
304
824
503
Показать ответСкрыть ответ
Два указателя: сумма ≤ L → пара. Эталон: 32 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — пары по среднему

Первая строка — число M, далее массы. Максимальное число пар, среднее арифметическое масс в которых не меньше M.

Данные (61 строк) — раскрыть и скопировать в файл
500
787
750
733
101
454
654
157
542
786
190
861
147
142
390
651
504
455
749
348
484
443
335
584
487
785
345
466
869
740
105
600
163
765
756
297
524
537
521
192
609
101
353
408
491
673
577
499
621
189
810
635
305
763
538
505
129
181
328
681
243
Показать ответСкрыть ответ
Условие суммы ≥ 2·M. Эталон: 25 Напиши код по шаблону, запусти на этих данных и сверь.
04

Контейнеры в порядке поступления

«Коробки, вагоны, грузовики» (next-fit)

Признаки в условии

  • «в порядке поступления»
  • «не помещается — новый контейнер», «сколько контейнеров»

Идея

Копим в текущий контейнер. Как только следующий предмет не влезает — закрываем контейнер и открываем новый. Порядок задан — сортировать нельзя.

Условие

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

Шаблон решения

with open('26.txt') as f:
    C = int(f.readline())                       # вместимость контейнера
    weights = list(map(int, f.read().split()))  # порядок из файла — НЕ сортируем

boxes, cur = 1, 0
for w in weights:
    if cur + w <= C:
        cur += w
    else:
        boxes += 1
        cur = w
print(boxes)        # А: сколько контейнеров
print(C - cur)      # Б: свободное место в последнем

Ловушки

  • Здесь нельзя сортировать — порядок зафиксирован.
  • Первый контейнер уже открыт: boxes = 1.

Тренировка

Задача 1 — упаковка коробок

Первая строка — вместимость C, далее веса предметов в порядке поступления. А. Число коробок. Б. Свободное место в последней.

Данные (59 строк) — раскрыть и скопировать в файл
2000
1019
246
700
760
844
598
307
481
705
771
1093
167
229
920
688
712
1072
554
965
614
434
963
790
821
774
454
197
759
509
650
918
402
796
651
764
513
686
428
484
536
326
350
442
589
677
335
346
388
695
1040
249
342
827
298
741
316
208
270
Показать ответСкрыть ответ
next-fit, ответ Б = C − cur. Эталон: А = 21    Б = 465 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — загрузка грузовиков

Первая строка — грузоподъёмность Cap, далее массы в порядке поступления. А. Сколько грузовиков. Б. Максимальная итоговая загрузка.

Данные (51 строк) — раскрыть и скопировать в файл
1000
106
325
417
328
198
522
224
123
289
374
570
423
138
125
179
300
542
301
413
421
506
282
276
338
177
409
558
311
594
399
122
244
382
277
134
117
430
492
432
529
394
191
100
525
501
375
434
322
469
398
Показать ответСкрыть ответ
next-fit, следим за макс. загрузкой. Эталон: А = 21    Б = 993 Напиши код по шаблону, запусти на этих данных и сверь.
05

Поиск минимального порога — через bisect

«Минимальная вместимость / число, при которой …»

Признаки в условии

  • «минимальное значение X, при котором …»
  • ответ — параметр, а не элемент данных

Идея

Поиск порога — двоичный поиск, но писать руками не нужно: за вас это сделает bisect. Описываем проверку «при такой вместимости V условие выполняется?» (она монотонна) и просим bisect_left найти первый подходящий V.

Условие

Брёвна по очереди грузят на платформы (каждое — в текущую, пока не превышен предел). Найдите минимальную грузоподъёмность платформы, при которой хватит не более K платформ.По мотивам задачи о минимальной вместимости.

Шаблон решения

from bisect import bisect_left

with open('26.txt') as f:
    K = int(f.readline())                    # допустимое число платформ
    logs = list(map(int, f.read().split()))  # в порядке поступления

def feasible(V):                             # хватает ли вместимости V?
    if max(logs) > V:
        return False
    boxes, cur = 1, 0
    for w in logs:
        if cur + w <= V:
            cur += w
        else:
            boxes += 1
            cur = w
    return boxes <= K

candidates = range(max(logs), sum(logs) + 1)
i = bisect_left(candidates, True, key=feasible)
print(candidates[i])                         # минимальная достаточная вместимость

Встроенные функции: модуль bisect

Модуль bisect делает двоичный поиск по отсортированному списку за O(log n). Применяется и к данным, и к «ответу».

  • bisect_left(a, x) — сколько элементов строго меньше x;
  • bisect_right(a, x) — сколько не больше x;
  • bisect_right(a, x) - bisect_left(a, x) — сколько раз встречается x;
  • insort(a, x) — вставка с сохранением сортировки.
from bisect import bisect_left, bisect_right
data.sort()
cnt = bisect_right(data, hi) - bisect_left(data, lo)   # сколько чисел в [lo, hi]

Параметр key= (Python 3.10+, в КЕГЭ поддерживается) и позволяет искать «по ответу»: диапазон candidates не перебираем целиком, а сразу ныряем в нужную точку.

Ловушки

  • candidates «отсортирован» по key (монотонный feasible).
  • Нижняя граница — не меньше max(logs).

Тренировка

Задача 1 — грузоподъёмность платформы

Первая строка — допустимое число платформ K, далее длины брёвен (грузят next-fit). Минимальная грузоподъёмность, при которой хватит ≤ K платформ.

Данные (71 строк) — раскрыть и скопировать в файл
12
100
274
332
417
407
482
315
304
131
123
118
439
500
216
240
162
324
97
477
91
309
248
434
262
426
239
473
119
89
364
370
132
172
498
319
201
97
425
86
97
125
124
119
294
316
446
228
142
206
337
114
224
495
64
225
355
425
116
165
379
403
384
188
196
295
109
138
494
118
447
Показать ответСкрыть ответ
bisect_left по диапазону вместимостей. Эталон: 1715 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — вместимость рейса

Первая строка — допустимое число рейсов K, далее размеры групп пассажиров. Минимальная вместимость автобуса, при которой хватит ≤ K рейсов.

Данные (65 строк) — раскрыть и скопировать в файл
10
91
101
96
31
113
103
62
54
55
82
104
105
90
71
62
94
90
96
27
39
61
72
82
20
37
79
69
49
89
22
95
31
45
120
53
59
86
104
110
119
47
117
22
103
106
69
84
119
30
51
95
117
40
74
114
22
97
117
20
50
32
44
84
74
Показать ответСкрыть ответ
Тот же bisect по вместимости. Эталон: 518 Напиши код по шаблону, запусти на этих данных и сверь.
06

Максимизация выгоды с группировкой

«Каждый k-й товар бесплатно»

Признаки в условии

  • «максимальная выручка / выгода»
  • «каждый второй / третий бесплатно»

Идея

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

Условие

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

Шаблон решения

prices = list(map(int, open('26.txt').read().split()))

prices.sort(reverse=True)        # дорогие вперёд
total = 0
for idx, p in enumerate(prices):
    if (idx + 1) % 3 != 0:       # каждый 3-й (3,6,9,...) — бесплатно
        total += p
print(total)                     # А: минимальная сумма к оплате
print(sum(prices) - total)       # Б: суммарная выгода

Ловушки

  • Направление сортировки зависит от того, чью выгоду максимизируем.
  • «Каждый 3-й» — позиции 3, 6, 9 → (idx+1) % 3 == 0.

Тренировка

Задача 1 — каждый третий бесплатно

Файл содержит цены товаров. По акции каждый третий товар бесплатен. А. Минимальная сумма к оплате. Б. Суммарная выгода.

Данные (48 строк) — раскрыть и скопировать в файл
1668
1032
3715
3620
2839
454
1648
992
3383
703
3733
654
4809
4797
1084
3318
1064
4922
3364
4605
678
3243
2297
2598
2084
3012
3187
4975
2423
4317
2907
2197
1370
510
148
3188
1919
4356
4496
952
3277
1455
1675
204
4025
3105
2549
571
Показать ответСкрыть ответ
Сортировка убыв., пропускаем каждый 3-й. Эталон: А = 81780    Б = 38342 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — каждый второй бесплатно

Цены товаров. По акции каждый второй бесплатен. А. Минимальная сумма к оплате. Б. Суммарная выгода.

Данные (50 строк) — раскрыть и скопировать в файл
1497
2399
1636
416
2271
2563
494
1570
2618
2723
930
95
2638
764
2113
1550
2380
2653
1835
1266
2116
1347
281
1554
1618
2813
262
1686
2780
1842
1923
2364
1357
1348
867
1880
375
349
354
2770
2054
226
1389
2431
751
1748
128
1607
1923
2702
Показать ответСкрыть ответ
То же, но бесплатен каждый 2-й. Эталон: А = 40232    Б = 39024 Напиши код по шаблону, запусти на этих данных и сверь.
07

Предпросчёт по диапазону значений

«Очень много запросов, а значения ограничены»

Признаки в условии

  • огромное N (до 10⁶) — на каждый элемент считать нельзя
  • параметр-запрос ограничен небольшим диапазоном (1..1000)

Идея

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

Условие

Для N участков покупают газонокосилки. В магазине K видов (мощность, цена). Для участка берут косилку мощностью не ниже требуемой, среди них самую дешёвую, при равной цене — самую мощную. Найдите сумму за косилки наибольшей мощности и число различных видов.По мотивам задачи о газонокосилках.

Шаблон решения

f = open('26.txt')
n, k = map(int, f.readline().split())
plots = []
for _ in range(n):
    plots.append(int(f.readline()))
mowers = []
for _ in range(k):
    power, cost = map(int, f.readline().split())
    mowers.append((power, cost))
f.close()

cache = {}                       # запоминаем ответ для каждого требования r

def best_mower(r):
    if r in cache:               # уже считали — берём готовое
        return cache[r]
    best = None                  # (cost, power) лучшей подходящей косилки
    for power, cost in mowers:
        if power >= r:
            if best is None or cost < best[0] or (cost == best[0] and power > best[1]):
                best = (cost, power)
    cache[r] = best              # запомнили на будущее
    return best

count = {}                       # сколько косилок каждого вида купили
for r in plots:
    cost, power = best_mower(r)
    key = (power, cost)
    count[key] = count.get(key, 0) + 1

max_power = 0
for power, cost in count:
    if power > max_power:
        max_power = power

total = 0
for (power, cost), amount in count.items():
    if power == max_power:
        total += cost * amount

print(total, len(count))         # сумма за самые мощные; число видов

Почему кеширование ускоряет

скоростьповторы считаем один раз

Без кеша для каждого из N участков пришлось бы заново перебирать все K косилок — до N·K операций. Но различных требований мало (не больше 1000), и многие участки повторяются. Словарь cache хранит уже найденные ответы: первый участок с требованием r запускает перебор, все следующие с тем же r получают результат мгновенно. Если требования не повторяются, кеш не помогает — тогда сворачивают виды в массив min_cost[мощность] и считают таблицу по значениям 1..1000.

Ловушки

  • Кешируй по значению требования r, а не по номеру участка — иначе кеш не сработает на повторах.
  • Ключ словаря count — пара (мощность, цена): одинаковые косилки складываются в один вид.
  • Правило при равной цене — бóльшая мощность; следи за порядком условий в if.

Тренировка

Задача 1 — газонокосилки

Первая строка — N и K. Далее N строк — нужная мощность для участка. Далее K строк — мощность и цена вида. А. Сумма за косилки наибольшей мощности (среди купленных). Б. Число различных видов.

Данные (101 строк) — раскрыть и скопировать в файл
70 30
822
294
418
11
718
922
791
579
919
914
155
172
368
501
784
897
579
99
552
817
706
547
370
962
356
990
76
522
536
719
127
700
608
287
673
586
552
711
921
874
697
233
703
821
393
575
636
228
316
967
415
657
719
853
153
37
393
176
931
584
991
638
72
288
510
528
572
808
842
203
1000 617
253 148
297 250
12 9
97 34
329 273
106 116
562 424
848 647
343 300
169 121
129 79
50 43
48 27
337 316
22 8
745 582
63 39
823 460
672 338
263 154
967 543
688 621
916 462
156 99
439 385
391 275
418 193
420 292
616 373
Показать ответСкрыть ответ
Кешируем лучшую косилку для каждого требования, затем подсчёт. Эталон: А = 1234    Б = 12 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — насосы

Формат тот же: N скважин, K видов насосов. Правило выбора то же. А. Общая сумма за все купленные насосы. Б. Число различных видов.

Данные (116 строк) — раскрыть и скопировать в файл
80 35
795
840
910
841
632
732
628
477
301
949
614
941
832
166
109
971
667
679
426
169
816
371
631
419
172
769
147
769
803
10
258
542
580
228
531
581
323
681
786
893
78
227
179
47
206
833
267
587
824
676
964
102
100
827
299
439
852
722
271
686
155
484
229
909
671
693
469
98
670
732
319
214
496
816
536
705
383
55
328
981
672 269
487 393
535 217
725 354
186 141
420 266
200 98
884 750
56 45
908 789
770 350
942 599
514 233
1000 484
624 373
420 197
222 89
801 292
282 184
906 447
836 539
823 340
623 260
478 358
681 511
938 465
507 251
142 76
949 316
226 115
842 349
825 500
898 341
489 368
293 119
Показать ответСкрыть ответ
То же кеширование, иной итог. Эталон: А = 18198    Б = 11 Напиши код по шаблону, запусти на этих данных и сверь.
08

Максимум непересекающихся событий

«Отрезки/мероприятия — сортировка по концу»

Признаки в условии

  • события заданы парами «начало — конец»
  • нужно максимальное количество непересекающихся (один зал)

Идея

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

Условие

Даны заявки на мероприятия с временем начала и окончания. Если интервалы пересекаются, провести можно только одно (касание концов допустимо). Найдите максимальное число мероприятий в одном зале и время окончания последнего.По мотивам задачи ЕГЭ о расписании мероприятий.

Шаблон решения

events = []
with open('26.txt') as f:
    n = int(f.readline())
    for _ in range(n):
        start, end = map(int, f.readline().split())
        events.append((start, end))

events.sort(key=lambda e: e[1])       # по правому концу (времени окончания)

count = 0
last_end = -1                          # конец последнего выбранного
for start, end in events:
    if start >= last_end:              # касание разрешено -> '>='
        count += 1
        last_end = end
print(count, last_end)                 # сколько событий; конец последнего

Левый конец: сколько нужно залов

Сортировка по правому концу даёт максимум непересекающихся. Сортировка по левому концу нужна для родственной задачи — «сколько залов, чтобы провести ВСЕ события» (максимум одновременных). Решается «сметанием»: отдельно сортируем начала и концы.

starts = sorted(s for s, e in events)
ends   = sorted(e for s, e in events)
i = j = 0
cur = best = 0
while i < len(starts):
    if starts[i] < ends[j]:   # новое началось раньше, чем освободилось -> +1 зал
        cur += 1
        best = max(best, cur)
        i += 1
    else:
        cur -= 1
        j += 1
print(best)                   # минимум залов = максимум одновременных

Камеры хранения: фиксированное число ячеек

Близкий «симуляционный» сюжет: есть K ячеек, пассажиры приходят со временем сдачи и выдачи. Багаж кладут в свободную ячейку с наименьшим номером; ячейка свободна со следующей минуты после выдачи; если свободных нет — отказ. Та же идея событий во времени, но ресурсов ровно K и часть заявок отклоняется.

Сортируем по времени сдачи и для каждого пассажира ищем младшую свободную ячейку (arrival > free_until[cell]):

with open('26.txt') as f:
    K = int(f.readline())                 # число ячеек
    n = int(f.readline())                 # число пассажиров
    people = []
    for _ in range(n):
        a, d = map(int, f.readline().split())   # время сдачи и выдачи
        people.append((a, d))

people.sort()                             # по времени сдачи (прибытия)

free_until = [0] * (K + 1)                # ячейки 1..K заняты по это время
count = 0
last_cell = 0
for a, d in people:
    for cell in range(1, K + 1):          # свободная ячейка с min номером
        if a > free_until[cell]:          # доступна со следующей минуты
            free_until[cell] = d
            count += 1
            last_cell = cell
            break
print(count, last_cell)                   # принято багажей; ячейка последнего

Ловушки

  • Сортируй по концу, не по началу и не по длине.
  • Касание разрешено → start >= last_end (не >).

Тренировка

Задача 1 — расписание мероприятий

Первая строка — число заявок N, далее N строк «начало конец» (1..1440). Касание концов допустимо. А. Максимальное число мероприятий в одном зале. Б. Время окончания последнего.

Данные (46 строк) — раскрыть и скопировать в файл
45
1116 1243
286 424
706 847
1028 1109
402 429
1204 1279
947 975
194 248
874 982
1067 1096
993 1168
700 775
712 791
226 300
261 325
836 860
754 909
874 1033
1181 1335
1014 1083
672 750
647 825
114 275
1252 1339
848 894
652 809
770 801
593 761
1106 1163
1352 1419
724 881
1314 1359
1096 1264
701 777
1223 1304
993 1130
25 198
616 751
584 685
267 442
739 844
80 206
751 778
753 923
755 882
Показать ответСкрыть ответ
Сортировка по концу, жадный выбор. Эталон: А = 11    Б = 1359 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — сколько нужно залов

Тот же формат. Нужно провести все мероприятия, при пересечении — в разных залах. Минимальное число залов (= максимум одновременных).

Данные (46 строк) — раскрыть и скопировать в файл
45
1163 1251
1204 1326
240 397
206 307
239 379
702 734
156 196
731 885
638 683
74 167
1113 1248
1199 1333
1377 1440
460 612
688 826
203 347
44 170
669 709
1205 1298
240 301
810 898
828 858
1203 1225
918 1050
966 1042
666 806
1118 1289
924 1035
116 171
505 550
963 984
272 424
37 146
307 384
1007 1184
1180 1316
1132 1162
749 804
724 859
1259 1293
827 958
955 1007
766 880
426 499
1374 1440
Показать ответСкрыть ответ
Сметание: сортируем начала и концы. Эталон: 8 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 3 — камеры хранения

Первая строка — число ячеек K, вторая — число пассажиров N, далее N строк «время сдачи, время выдачи» (минуты, 1..1440). Багаж кладут в свободную ячейку с наименьшим номером; ячейка свободна со следующей минуты после выдачи; если свободных нет — пассажиру отказывают. А. Сколько багажей будет принято. Б. Номер ячейки, в которую сдаст багаж последний принятый пассажир.

Данные (42 строк) — раскрыть и скопировать в файл
5
40
523 788
385 595
876 978
1385 1440
1092 1372
1388 1440
883 961
125 353
675 808
952 1053
723 844
181 266
1196 1440
1280 1440
1411 1440
982 1020
1359 1417
632 763
1080 1313
1146 1353
1168 1220
578 868
31 98
546 722
1305 1440
1284 1426
271 365
53 193
590 652
122 328
1159 1440
467 759
437 680
206 448
570 661
1027 1222
161 405
185 228
622 800
463 696
Показать ответСкрыть ответ
Сортируем по времени сдачи; для каждого ищем младшую ячейку с arrival > free_until. Эталон: А = 28    Б = 4 Напиши код по шаблону, запусти на этих данных и сверь.
09

Слияние пересекающихся отрезков

«Длина покрытия, число участков»

Признаки в условии

  • отрезки «начало — конец», нужно объединить пересекающиеся
  • «суммарная длина», «число непрерывных участков», «разрывы»

Идея

Сортируем по левому концу и ведём один объединённый отрезок. Если следующий начинается не позже его конца — раздвигаем правый край; иначе закрываем участок (копим длину) и начинаем новый.

Условие

Устройство было активно на интервалах «начало–конец», которые могут пересекаться. Найдите суммарную длительность активности (пересечения — один раз) и число непрерывных периодов.По мотивам задачи об интервалах активности.

Шаблон решения

intervals = []
with open('26.txt') as f:
    n = int(f.readline())
    for _ in range(n):
        s, e = map(int, f.readline().split())
        intervals.append((s, e))

intervals.sort()                   # по левому концу (началу)

total = 0                          # суммарная длина покрытия
segments = 0                       # число непрерывных участков
cur_s, cur_e = intervals[0]
for s, e in intervals[1:]:
    if s <= cur_e:                 # пересекаются или касаются -> раздвигаем
        if e > cur_e:
            cur_e = e
    else:                          # разрыв -> закрываем участок
        total += cur_e - cur_s
        segments += 1
        cur_s, cur_e = s, e
total += cur_e - cur_s             # последний участок
segments += 1
print(total, segments)

Самый длинный участок и самый длинный разрыв

Тем же проходом собери объединённые участки в список merged и ответь на соседние вопросы:

longest_seg = 0
for s, e in merged:
    if e - s > longest_seg:
        longest_seg = e - s

longest_gap = 0
for k in range(1, len(merged)):
    gap = merged[k][0] - merged[k - 1][1]
    if gap > longest_gap:
        longest_gap = gap

Связь с раз­делом 08: там сортировали по правому концу, чтобы выбрать; здесь по левому, чтобы склеить.

Ловушки

  • Касание s <= cur_e объединяет (нет разрыва).
  • Последний участок закрывается уже после цикла — легко забыть.

Тренировка

Задача 1 — время активности

Первая строка — число записей N, далее N строк «начало конец»; интервалы могут пересекаться. А. Суммарная длительность активности. Б. Число непрерывных периодов.

Данные (25 строк) — раскрыть и скопировать в файл
24
743 804
2710 2854
1371 1462
811 966
2574 2730
750 902
1211 1358
1084 1164
1040 1100
1329 1403
961 1034
973 1054
1498 1657
827 983
886 992
1086 1117
1404 1537
903 1036
2204 2322
2431 2530
1105 1212
2261 2320
1153 1305
2327 2485
Показать ответСкрыть ответ
Слияние по левому концу. Эталон: А = 1515    Б = 4 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — длиннейший период и разрыв

Тот же формат. А. Длина самого длинного непрерывного периода. Б. Длина самого длинного промежутка без активности между первым началом и последним концом.

Данные (25 строк) — раскрыть и скопировать в файл
24
1891 2040
1048 1174
872 929
1390 1443
75 156
1354 1443
1631 1736
201 248
1332 1476
2555 2663
1534 1598
1998 2057
783 907
2192 2263
1531 1654
1375 1518
748 798
2483 2514
598 747
2036 2112
2457 2609
2713 2858
2263 2393
722 867
Показать ответСкрыть ответ
Собираем merged, ищем max длину и max gap. Эталон: А = 331    Б = 350 Напиши код по шаблону, запусти на этих данных и сверь.
10

Вложение «матрёшкой» — цепочка с порогом

«Коробки вкладываются с запасом D»

Признаки в условии

  • размер (сторона коробки), вкладываются друг в друга
  • вложение разрешено, если размер меньше хотя бы на D

Идея

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

Условие

Кубическую коробку можно вложить в другую, если её сторона меньше хотя бы на D. Подарок помещается в самую маленькую коробку. Найдите наибольшее число вложенных коробок и сторону самой маленькой.По мотивам задачи ЕГЭ об упаковке матрёшкой.

Шаблон решения

with open('26.txt') as f:
    n, D = map(int, f.readline().split())   # D — минимальная разница сторон
    sides = []
    for _ in range(n):
        sides.append(int(f.readline()))

sides.sort(reverse=True)           # по убыванию: снаружи внутрь

count = 0
last = None                        # сторона последней вложенной коробки
for s in sides:
    if last is None or s <= last - D:   # помещается с запасом не меньше D
        count += 1
        last = s
print(count, last)                 # число коробок; сторона самой маленькой

Настоящий двумерный случай

Если у коробки две стороны, она входит в другую, только когда меньше и ширина, и высота. Тогда ищем самую длинную цепочку, где растут оба размера (наибольшая возрастающая подпоследовательность):

boxes.sort(key=lambda b: (b[0], -b[1]))   # по ширине; при равной — высота убыв.
best = 0
longest = [1] * n
for i in range(n):
    for j in range(i):
        if boxes[j][0] < boxes[i][0] and boxes[j][1] < boxes[i][1]:
            if longest[j] + 1 > longest[i]:
                longest[i] = longest[j] + 1
    if longest[i] > best:
        best = longest[i]
print(best)

Сортировка по высоте по убыванию при равной ширине не даёт двум коробкам одинаковой ширины попасть в одну цепочку.

Ловушки

  • Разница ровно D подходит (<=), меньше — нет.
  • Ответ Б — это last после цикла (последняя взятая).

Тренировка

Задача 1 — кубические коробки

Первая строка — число коробок N и минимальная разница D. Далее N строк — стороны кубических коробок. А. Наибольшее число коробок в матрёшке. Б. Максимально возможная сторона самой маленькой коробки.

Данные (61 строк) — раскрыть и скопировать в файл
60 10
69
292
392
33
131
61
254
390
231
242
334
195
108
49
250
15
200
222
312
391
393
2
357
229
137
370
118
303
53
163
16
12
14
333
278
5
196
352
111
217
372
15
271
114
392
225
254
284
120
177
119
347
113
390
236
149
12
214
285
329
Показать ответСкрыть ответ
Сортировка убыв., порог last − D. Эталон: А = 24    Б = 5 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — двумерные коробки

Первая строка — число коробок N, далее N строк «ширина высота». Коробка входит в другую, только если и ширина, и высота строго меньше. Наибольшее число вложенных коробок.

Данные (41 строк) — раскрыть и скопировать в файл
40
912 538
215 232
793 993
494 68
343 3
871 186
971 38
220 780
762 651
808 404
950 561
234 419
343 286
615 888
817 425
147 313
745 869
263 174
4 948
73 666
146 504
45 668
717 722
674 696
167 523
205 913
904 23
23 457
303 935
649 742
464 611
78 84
241 563
273 207
351 334
812 128
326 201
832 707
208 111
757 88
Показать ответСкрыть ответ
НВП по высоте после сортировки по ширине. Эталон: 11 Напиши код по шаблону, запусти на этих данных и сверь.
11

Многокритериальная сортировка

«Сортировка по нескольким ключам и вывод по местам»

Признаки в условии

  • «места по убыванию … при равенстве — по …»
  • нужен номер объекта, а не только значение
  • «кто на K-м месте», «сколько с такими же показателями»

Идея

Храним каждый объект вместе с его номером — кортеж (показатель, …, номер). Сортируем сразу по нескольким ключам через key=lambda: для убывания ставим минус перед числом, для возрастания — оставляем как есть. После сортировки место K — это элемент data[K-1] (нумерация с нуля).

Условие

На соревнованиях для каждого участника известны результат и его номер. Места распределяют по убыванию результата, при равенстве — выше тот, чей номер меньше. Найдите номер участника, занявшего K-е место, и его результат.По мотивам задач о рейтингах и турнирных таблицах.

Шаблон решения

data = []
with open('26.txt') as f:
    n = int(f.readline())
    for num in range(1, n + 1):          # num — порядковый номер (с 1)
        score = int(f.readline())
        data.append((score, num))        # храним результат ВМЕСТЕ с номером

# сортировка по двум ключам:
# по убыванию результата (минус), при равенстве — по возрастанию номера
data.sort(key=lambda t: (-t[0], t[1]))

K = 5
score_k, num_k = data[K - 1]             # место K — это индекс K-1 (с нуля)
print(num_k, score_k)                    # номер участника на K-м месте; его результат

Как задать несколько ключей сразу

key=lambda возвращает кортеж — Python сравнивает кортежи поэлементно: сначала первый ключ, при равенстве — второй, и так далее. Направление задаём знаком:

  • по убыванию числа — минус: -score;
  • по возрастанию — как есть: num.
data.sort(key=lambda t: (-t[0], -t[2], t[1]))
# по убыв. 1-го показателя; 
# при равенстве по убыв. 2-го; 
# затем по возр. номера

Чтобы после перестановки знать, кто на этом месте, номер кладут в кортеж заранее: (показатель, номер). Если минус не подходит (например, ключ — строка), сортируют в несколько проходов, опираясь на устойчивость sort: сначала по младшему ключу, потом по старшему — равные сохраняют прежний порядок.

Ловушки

  • Без номера в кортеже после сортировки не узнаешь, кто на месте — клади (значение, номер) сразу.
  • Минус задаёт убывание только для чисел; для разнонаправленных нечисловых ключей сортируй в несколько проходов (устойчивость sort).
  • Место K — это индекс K-1 (нумерация с нуля).
  • «При равенстве — по номеру» легко забыть: без этого ключа порядок равных по условию не определён.

Тренировка

Задача 1 — место на соревнованиях

Первая строка — число участников N, далее N строк с результатами (номер участника = порядок в файле, с 1). Места распределяют по убыванию результата, при равенстве — выше тот, чей номер меньше. А. Номер участника, занявшего 5-е место. Б. Его результат.

Данные (51 строк) — раскрыть и скопировать в файл
50
35
9
31
3
36
36
74
57
3
35
21
98
90
54
22
94
81
32
50
17
17
27
20
70
100
72
63
17
52
3
3
19
63
49
84
91
11
100
31
42
45
8
20
57
97
14
61
11
90
68
Показать ответСкрыть ответ
Храним (результат, номер); сортируем по (-результат, номер); берём data[4]. Эталон: А = 16    Б = 94 Напиши код по шаблону, запусти на этих данных и сверь.
Задача 2 — группа с такими же баллами

Первая строка — число участников N, далее N строк с баллами (номер = порядок в файле). Места — по убыванию баллов, при равенстве — по возрастанию номера. А. Номер участника на 10-м месте. Б. Сколько всего участников набрали столько же баллов, сколько участник на 10-м месте.

Данные (61 строк) — раскрыть и скопировать в файл
60
4
18
27
25
24
2
8
3
15
24
14
15
20
12
25
6
3
15
0
28
26
12
13
19
24
24
0
22
14
8
23
25
7
18
30
3
28
10
0
0
0
20
17
0
30
28
12
21
6
13
23
0
16
7
24
14
30
15
17
7
Показать ответСкрыть ответ
Сортируем по (-баллы, номер); берём 10-й, затем считаем всех с такими же баллами. Эталон: А = 15    Б = 3 Напиши код по шаблону, запусти на этих данных и сверь.
Одиннадцать разделов держатся на одном каркасе: прочитать файл, отсортировать, пройти один раз и вывести ровно нужное. Данные в тренировке сгенерированы и перекрёстно проверены на Python (жадность сверена с ДП, слияние — с поклеточной проверкой, порог — с линейным перебором). Пиши код по шаблону, проверь на примере из условия, затем запусти на данных задачи и сверь ответ на тренажёре SilverTests.
Печать