Подтема 4 из 7
Ряды, серии и группы
Позиции по рядам в словаре, свободные места по разности соседних, столбцы, непрерывные серии и накопление по ключу.
Позиции по рядам
В строках файла — номер ряда и номер занятого места, вперемешку. Как узнать, что происходит в каждом ряду?
Разложить позиции по рядам в словарь «ряд → множество мест», а потом в каждом ряду отсортировать места и смотреть на соседние.
Ключ — номер ряда, а позиции удобно хранить : оно хранит только различные значения.
data = [(5, 3), (9, 13), (5, 2), (5, 8), (9, 10), (5, 4), (9, 20), (5, 3)]rows = {}for row, position in data: if row not in rows: rows[row] = set() rows[row].add(position)for row in sorted(rows): print(row, sorted(rows[row]))if row not in rows проверяет, встречался ли такой ряд; rows[row] = set() создаёт для нового ряда пустое множество, rows[row].add(position) добавляет позицию. Повторное попадание (5, 3) новой позиции не создало.
Повторы можно удалять только тогда, когда они не влияют на смысл задачи. Выбери, какие данные повторяются:
- несколько попаданий в одну светящуюся точку
- да, если важен только факт свечения
- несколько записей об одном занятом месте
- да, если это повторные сведения
- два файла одинакового размера
- нет, это два файла
- два товара одинаковой цены
- нет, это два товара
- несколько заявок на один объект
- зависит от того, нужны ли ID и число заявок
Если для одинаковой позиции нужно сохранить, например, минимальный номер заявки, одного множества недостаточно. Нужно хранить соответствие «позиция → минимальный ID заявки» — тоже словарём.
Свободные места
В ряду заняты места 4, 7, 12, 16. Где есть ровно два свободных места подряд между занятыми?
Между соседними занятыми местами a и b свободно b − a − 1 мест: ровно два — там, где b − a = 3.
Между местами 4 и 7 свободны 5 и 6: сами занятые места в промежуток не входят.
Соседними здесь называются соседние элементы отсортированного списка занятых мест, а не соседние строки файла.
Для условия «выбрать максимальный номер ряда, а внутри него минимальный номер подходящего свободного места» можно сразу задать нужный порядок просмотра: ряды по убыванию, места по возрастанию. Тогда подходит первое найденное решение:
data = [(5, 4), (8, 11), (5, 12), (8, 3), (5, 7), (8, 8), (5, 16)]places = sorted(set(data), key=lambda x: (-x[0], x[1]))answer = Nonefor i in range(len(places) - 1): row1, seat1 = places[i] row2, seat2 = places[i + 1] if row1 == row2 and seat2 - seat1 == 3: answer = (row1, seat1 + 1) breakprint(*answer)Сама сортировка уже учитывает приоритеты ответа. Проверка row1 == row2 обязательна: соседние записи списка могут относиться к разным рядам. Выводим seat1 + 1, потому что seat1 — занятое место, а нужно первое свободное после него. В ряду 5 тоже есть пара 4 и 7, но ряд 8 больше и просматривается раньше.
Различай условия — выбери формулировку:
- ровно два свободных, с обеих сторон занятые
b - a == 3- не менее двух свободных между занятыми
b - a >= 3- любые два соседних свободных места
- подходят и части длинного промежутка, и края ряда
Столбцы
«Перед креслом свободны все кресла с тем же номером» — как искать, если важны не места в ряду, а один номер кресла во всех рядах?
Собрать столбец: ключом словаря служит номер кресла, а значениями — номера рядов. Дальше всё как с местами в ряду.
Так же решается и «перед ним как можно больше свободных кресел подряд». Ряды нумеруются от сцены, поэтому «перед» значит «в рядах с меньшими номерами».
Пусть в зале 10 рядов, а кресло номер 5 занято в рядах 3 и 7. Свободные участки столбца — ряды 1–2, 4–6 и 8–10. Перед креслом в конце участка свободны все остальные кресла участка: в ряду 6 перед ним свободны ряды 4 и 5. Чтобы не обрабатывать отдельно последний участок, после всех занятых рядов добавляют условный занятый ряд M + 1, а до них считают занятым ряд 0:
m = 10column = [3, 7]best = Noneprev = 0for occupied in sorted(column) + [m + 1]: if occupied - prev >= 2: free_before = occupied - prev - 2 seat_row = occupied - 1 if best is None or free_before > best[0]: best = (free_before, seat_row) prev = occupiedprint(*best)Участок от prev + 1 до occupied − 1 содержит occupied − prev − 1 свободных кресел, а перед последним из них — на одно меньше. Строгое сравнение > оставляет ряд ближе к сцене при равенстве: участок 8–10 тоже даёт 2, но ряд 10 дальше. Совсем пустой столбец даёт M − 1 свободных кресел перед последним рядом — его нельзя пропустить, хотя в файле этого номера нет.
Серии
Отмечены позиции 2, 3, 4, 8, 9, 10, 11, 20. Как найти самую длинную непрерывную серию?
Идти по отсортированным позициям со счётчиком серии: следующая позиция на 1 больше — серия растёт, иначе начинается заново с 1.
Здесь три непрерывные серии — 2–4, 8–11 и 20 — длиной 3, 4 и 1.
Код для максимальной длины серии:
positions = sorted(set([10, 2, 3, 4, 8, 9, 11, 20, 3]))current = 0best = 0previous = Nonefor position in positions: if previous is not None and position == previous + 1: current += 1 else: current = 1 if current > best: best = current previous = positionprint(best)Почему при разрыве current = 1? Потому что текущая позиция уже начинает новую серию. Повтор позиции 3 убран множеством: дважды светящаяся точка — одна точка.
Если нужно количество максимальных непрерывных серий длиной не менее трёх, серию учитывают в момент разрыва — и ещё раз после цикла. Пройди программу по шагам на позициях, где последняя серия заканчивает ряд:
positions = [5, 8, 9, 10]line_count = 0current = 0previous = Nonefor position in positions: if previous is not None and position == previous + 1: current += 1 else: if current >= 3: line_count += 1 current = 1 previous = positionif current >= 3: line_count += 1print(line_count)Последняя проверка после цикла обязательна. Серия обычно учитывается при появлении разрыва, а после последнего элемента нового значения уже нет — завершающего разрыва программа не увидит, и единственная линия 8, 9, 10 потерялась бы: программа вывела бы 0. В подборке есть именно такие задачи о линиях, ограниченных тёмными точками или краем экрана: длинная непрерывная серия считается одной линией.
Одна и та же серия 10, 11, 12, 13, 14 даёт разные ответы в зависимости от вопроса — выбери вопрос:
- максимальную длину серии
- 5
- максимальные непрерывные серии длиной ≥ 3
- 1
- все последовательные тройки внутри неё
- 310 11 12, 11 12 13, 12 13 14
Для сравнения результатов по рядам два приоритета записывают явно: больше линий, а при равенстве больше номер ряда — line_count > best_count or (line_count == best_count and row > best_row). То же самое делает сравнение кортежей (line_count, row) > (best_count, best_row).
Линия с промежутком
А если внутри линии разрешены тёмные точки — не больше k подряд?
Это та же серия, только продолжается она, пока промежуток между соседними светлыми точками не больше k.
Длина такой линии — количество всех её точек, светлых и тёмных. Тогда новая позиция продолжает серию, если position - previous <= k + 1: между ними position − previous − 1 ≤ k тёмных точек. Длина линии — последняя позиция − первая + 1, а не число отмеченных позиций.
positions = [2, 3, 7, 8, 15]k = 3best = 0first = previous = Nonefor position in positions: if previous is None or position - previous > k + 1: first = position best = max(best, position - first + 1) previous = positionprint(best)Между 3 и 7 три тёмные точки — допустимо, линия идёт от 2 до 8 и содержит 7 точек. Между 8 и 15 тёмных шесть, поэтому 15 начинает новую линию.
Изолированная точка
Как найти светлые точки, у которых все другие светлые точки ряда дальше чем на D?
Достаточно проверить двух ближайших соседей в отсортированном ряду: если и они дальше D, остальные — тем более.
Для позиции с индексом i отсортированного списка:
positions = [2, 3, 10, 20, 24]distance = 4count = 0for i in range(len(positions)): left_ok = (i == 0 or positions[i] - positions[i - 1] > distance) right_ok = (i == len(positions) - 1 or positions[i + 1] - positions[i] > distance) if left_ok and right_ok: count += 1print(count)Изолирована только точка 10. У точки 20 сосед 24 на расстоянии ровно 4 — «дальше чем на 4» не выполнено. Проверки i == 0 и i == len(positions) - 1 стоят первыми: у крайних точек соседа с одной стороны нет, и короткое вычисление or не обращается к несуществующему индексу.
Накопление по ключу
Нужен лидер продаж: по каждому артикулу — сколько продано и сколько осталось. Как собрать эти числа?
Накапливать суммы и количества в словаре по ключу-артикулу, а лучший ключ выбирать кортежем приоритетов.
Например, по каждому товару известны артикул, цена и статус: 0 — продан, 1 — ещё в магазине. Дорогими считаются товары дороже средней цены всех товаров. Нужно найти лидера продаж среди дорогих артикулов: больше всего проданных единиц, при равенстве — дороже, затем — меньше осталось. Количества по артикулу считают через :
items = [(101, 500, 0), (101, 500, 0), (101, 500, 1), (205, 900, 0), (205, 900, 1), (205, 900, 1), (307, 120, 0), (307, 120, 0), (307, 120, 0), (412, 700, 0), (412, 700, 0), (412, 700, 1)]average = sum(p for _, p, _ in items) / len(items)sold, left, price = {}, {}, {}for art, p, status in items: price[art] = p if status == 0: sold[art] = sold.get(art, 0) + 1 else: left[art] = left.get(art, 0) + 1best = max((sold.get(a, 0), price[a], -left.get(a, 0), a) for a in price if price[a] > average)print(best[3], best[0] * best[1], -best[2])Средняя цена 555, дорогих артикулов два: 205 и 412. У 412 продано больше — 2 единицы на 1400 рублей, осталась одна. Артикул 307 продан трижды, но он дешёвый и в выбор не входит. Кортеж (продано, цена, −осталось, артикул) записывает приоритеты так же, как ключ сортировки в рейтинге: минус у «меньше осталось».
Так же решаются задачи о домах и подъездах: словарь «дом → (подъезд → номер заявки)» хранит для каждого подъезда наибольший или наименьший номер заявки, а самая длинная серия подряд идущих подъездов ищется счётчиком серии.
Итого
- По рядамсловарь «ряд → множество позиций»
- Свободные местамежду a и b свободно b − a − 1
- Столбецключ — номер кресла, значения — ряды
- Серия+1 — продолжение, иначе заново; после цикла — ещё раз
- Изолированная точкапроверить двух ближайших соседей
- По ключусуммы в словаре, лучший — кортежем приоритетов
Проверьте себя
Ответь сначала сам, потом открой ответ.
ОтветСкрыть ответ
ОтветСкрыть ответ
ОтветСкрыть ответ
ОтветСкрыть ответ
ОтветСкрыть ответ
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Ровно три свободных места
Заняты места (ряд, место): (3, 4), (3, 8), (3, 15), (7, 2), (7, 9), (7, 13), (7, 20), (7, 24), (5, 1), (5, 5). Нужны ровно три свободных места подряд, слева и справа от которых места заняты. Найди наибольший номер ряда, где такие места есть, и наименьший номер первого из трёх свободных мест в нём.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Между занятыми a и b ровно три свободных, если b − a = 4.
Решение
Ряд 7 — наибольший: между 9 и 13 свободны 10, 11, 12, а между 20 и 24 — 21, 22, 23. Наименьший номер первого места — 10.
Решение программой
taken = [(3, 4), (3, 8), (3, 15), (7, 2), (7, 9), (7, 13), (7, 20), (7, 24), (5, 1), (5, 5)]rows = {}for r, s in taken: rows.setdefault(r, []).append(s)for r in sorted(rows, reverse=True): seats = sorted(rows[r]) found = [a + 1 for a, b in zip(seats, seats[1:]) if b - a == 4] if found: print(r, found[0]) breakОтвет: 7 10
Два свободных места
Заняты места (ряд, место): (5, 10), (5, 13), (8, 20), (8, 23), (8, 30), (8, 33). Нужны два свободных соседних места, непосредственно ограниченных занятыми. Выбери максимальный ряд и минимальный подходящий номер первого свободного места.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Между занятыми x и y ровно два свободных, если y − x − 1 == 2.
Решение
8 21: в восьмом ряду первая подходящая пара — 21 и 22.
Решение программой
taken = [(5, 10), (5, 13), (8, 20), (8, 23), (8, 30), (8, 33)]rows = {}for r, s in taken: rows.setdefault(r, []).append(s)found = Nonefor r in sorted(rows, reverse=True): s = sorted(rows[r]) for x, y in zip(s, s[1:]): if y - x - 1 == 2: found = (r, x + 1) break if found: breakprint(*found)Ответ: 8 21
Четыре свободных места
В файле 26-8.txt в первой строке записаны количество занятых мест N, число рядов M и число мест в ряду K, в каждой следующей — номер ряда и номер занятого места. Найди ряд с наибольшим номером, в котором есть ровно 4 свободных места подряд, таких, что непосредственно слева и справа от них места заняты. Запиши номер этого ряда, затем наименьший номер первого места из таких четвёрок в этом ряду.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Ровно четыре свободных между занятыми: y − x − 1 == 4.
Решение
1496 49: разность соседних занятых мест равна 5; с проверкой y - x == 4 нашёлся бы другой ряд.
Решение программой
with open('26-8.txt') as f: f.readline() data = [list(map(int, line.split())) for line in f] seats = {}for r, s in data: seats.setdefault(r, set()).add(s)found = Nonefor r in sorted(seats, reverse=True): s = sorted(seats[r]) for x, y in zip(s, s[1:]): if y - x - 1 == 4: found = (r, x + 1) break if found: breakprint(*found)Ответ: 1496 49
Кресло номер 5
В зале 10 рядов, кресло номер 5 занято в рядах 3 и 7. В каком ряду купить кресло номер 5, чтобы перед ним было как можно больше свободных кресел с тем же номером, и сколько их? При равенстве — ряд ближе к сцене.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Добавь условные занятые ряды 0 и 11; лучшее место — перед следующим занятым.
Решение
Ряд 6, перед ним 2 свободных кресла (ряды 4 и 5); ряд 10 тоже даёт 2, но он дальше от сцены.
Решение программой
taken = [3, 7]best = Noneprev = 0for o in taken + [11]: if o - prev >= 2: cand = (o - prev - 2, -(o - 1)) if best is None or cand > best: best = cand prev = oprint(-best[1], best[0])Ответ: 6 2
Серии позиций
Позиции 1, 2, 3, 7, 8, 9, 10, 15, 16. Найди максимальную длину серии и число максимальных непрерывных серий длиной хотя бы 3.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Серия продолжается, если позиция на 1 больше предыдущей; последнюю серию учти после цикла.
Решение
4 2: серии имеют длины 3, 4 и 2.
Решение программой
p = [1, 2, 3, 7, 8, 9, 10, 15, 16]runs = []current = 1for i in range(1, len(p)): if p[i] == p[i - 1] + 1: current += 1 else: runs.append(current) current = 1runs.append(current)print(max(runs), sum(r >= 3 for r in runs))Ответ: 4 2
Линии на экране
В файле 26-9.txt записан протокол попаданий частиц в экран: в первой строке — количество попаданий N, в каждой следующей — номер ряда и номер позиции (в одну точку могло попасть несколько частиц). Точка, в которую попала хотя бы одна частица, светлая, остальные тёмные. Линией называют не менее трёх светлых точек подряд в одном ряду, слева и справа от которых тёмная точка или край экрана. Определи наибольшее количество линий в одном ряду и номер этого ряда; если таких рядов несколько, укажите наибольший номер.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Позиции ряда — множество; серии считают по отсортированным позициям, последнюю — после цикла.
Решение
10 132: последнюю серию ряда проверяют после цикла, иначе ответ был бы другим.
Решение программой
with open('26-9.txt') as f: f.readline() data = [list(map(int, line.split())) for line in f] screen = {}for r, p in data: screen.setdefault(r, set()).add(p)best = (0, 0)for r, ps in screen.items(): p = sorted(ps) lines = 0 current = 1 for i in range(1, len(p) + 1): if i < len(p) and p[i] == p[i - 1] + 1: current += 1 else: if current >= 3: lines += 1 current = 1 best = max(best, (lines, r))print(*best)Ответ: 10 132
Линия с тёмными точками
Позиции светлых точек 1, 2, 5, 6, 7, 11, 12; между светлыми точками линии может быть не больше двух тёмных подряд. Найди наибольшую длину линии (светлые и тёмные точки вместе).
Введите число.
Подсказка
Линия обрывается, если между соседними светлыми больше двух тёмных.
Решение
7: точки 1–7 образуют одну линию (между 2 и 5 две тёмные), а 11 и 12 — другую.
Решение программой
p = [1, 2, 5, 6, 7, 11, 12]start = p[0]best = 0for i in range(len(p)): if i > 0 and p[i] - p[i - 1] - 1 > 2: start = p[i] best = max(best, p[i] - start + 1)print(best)Ответ: 7
Изолированные точки
Позиции 1, 6, 8, 15, 30, 33. Сколько точек изолированы, если другие точки должны быть дальше чем на 3?
Введите число.
Подсказка
Достаточно ближайших соседей слева и справа.
Решение
2: изолированы 1 и 15; у 30 и 33 расстояние ровно 3.
Решение программой
p = [1, 6, 8, 15, 30, 33]count = 0for i in range(len(p)): left = i == 0 or p[i] - p[i - 1] > 3 right = i == len(p) - 1 or p[i + 1] - p[i] > 3 if left and right: count += 1print(count)Ответ: 2
Изолированные точки экрана
Для файла 26-9.txt светлая точка называется изолированной, если остальные светлые точки её ряда находятся от неё дальше чем на 4 позиции или их нет. Определи наибольшее количество изолированных точек в одном ряду и номер этого ряда; если таких рядов несколько, укажите наименьший номер.
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
При равном количестве нужен наименьший номер ряда — сравнивайте (количество, −ряд).
Решение
22 145: ближайшие соседи слева и справа должны быть дальше чем на 4.
Решение программой
with open('26-9.txt') as f: f.readline() data = [list(map(int, line.split())) for line in f] screen = {}for r, p in data: screen.setdefault(r, set()).add(p)best = Nonefor r, ps in screen.items(): p = sorted(ps) count = 0 for i in range(len(p)): left = i == 0 or p[i] - p[i - 1] > 4 right = i == len(p) - 1 or p[i + 1] - p[i] > 4 if left and right: count += 1 if best is None or (count, -r) > best: best = (count, -r)print(best[0], -best[1])Ответ: 22 145