EduBrick
классная работа

Накопление: сумма, произведение, количество

Программирование на Python: от нуля до олимпиад

0/18решено 0 из 18до зачёта осталось 10

Чему научитесь

  • Собирать ответ по ходу цикла, не храня все данные
  • Читать поток чисел по одному и сразу их обрабатывать
  • Считать сумму, произведение и количество подходящих
  • Искать максимум и минимум так, чтобы отрицательные числа не ломали ответ

Как устроено занятие

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

Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.

После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.

Сколько это займёт

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

теория

Ответ, которого ещё нет

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

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

s = 0                    # место для ответа — до цикла

for i in range(1, 6):
    s = s + i            # каждый шаг что-то добавляет

print(s)                 # 15, один раз и в конце

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

Запись s = s + i встречается так часто, что для неё есть короткая форма: s += i. Так же работают -=, *=, //=.

теория

Три накопителя, которые нужны всегда

Почти любая задача на цикл — это один из трёх накопителей или их сочетание.

что копим с чего начать шаг цикла
сумму s = 0 s += x
произведение p = 1 p *= x
количество count = 0 count += 1, но только если условие выполнено

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

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

count = 0

for i in range(1, n + 1):
    if i % 7 == 0:
        count += 1        # прибавляем не всегда, а по условию

print(count)
тест

Проверка: с чего начинать

Программа должна перемножить пять введённых чисел. С какого значения нужно начать накопитель?

Войдите, чтобы ответить.
теория

Читаем числа по одному

До сих пор входные данные помещались в одну-две строки. Теперь их много, и читать их надо в цикле:

n = int(input())          # сколько чисел будет
s = 0

for _ in range(n):        # ровно n раз
    x = int(input())      # очередное число
    s += x

print(s)

Подчёркивание вместо имени переменной — обычное соглашение: «номер шага мне не нужен, важно только количество повторений». Можно написать и for i in range(n), никакой ошибки не будет.

Обратите внимание, чего здесь нет: нигде не хранятся все сто чисел. Каждое живёт один шаг цикла и заменяется следующим. Накопитель — единственное, что переживает шаг.

Это не экономия ради экономии. В олимпиадных задачах чисел бывает миллион, и «сохраню всё, потом разберусь» упирается в память. Привычка собирать ответ по ходу пригодится дальше постоянно.

теория

Максимум: почему нельзя начинать с нуля

Поиск наибольшего — тот же накопитель, только вместо сложения сравнение:

best = 0

for _ in range(n):
    x = int(input())
    if x > best:
        best = x

print(best)

На числах 3, 8, 5 программа выдаст 8 — верно. На числах −3, −8, −5 она выдаст 0, которого во входных данных вовсе не было.

Причина в начальном значении: ноль оказался больше всех настоящих чисел, и ни одно из них не смогло его вытеснить. Ошибка коварная тем, что на «нормальных» тестах не видна.

Два надёжных способа

Первое число как начало. Читаем его отдельно, до цикла, и сравниваем с остальными.

Признак «ещё ничего не встречалось». В Python для этого есть None:

best = None

for _ in range(n):
    x = int(input())
    if best is None or x > best:
        best = x

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

тест

Проверка: поиск наибольшего

Программа ищет наибольшее из введённых чисел, начиная с best = 0.

На вход подали −7, −2, −9. Что она выведет и почему?

Войдите, чтобы ответить.
теория

Помнить предыдущее

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

Хранить весь набор для этого не нужно, хватает одной дополнительной переменной:

previous = None
count = 0

for _ in range(n):
    x = int(input())
    if previous is not None and x > previous:
        count += 1
    previous = x            # ключевая строка: готовимся к следующему шагу

print(count)

Последняя строка тела — самая важная и самая забываемая. Без неё previous навсегда останется первым числом, и программа посчитает совсем другое.

На первом шаге предыдущего числа ещё нет — отсюда проверка на None. Пар всегда на одну меньше, чем чисел.

теория

Три ошибки этого занятия

Накопитель внутри цикла. s = 0 оказалось в теле — и обнуляется каждый шаг. Признак: ответ равен последнему числу, а не сумме.

Печать внутри цикла. print сдвинут в тело — вместо одного числа выводится вся история накопления. Ответ в последней строке верный, но вердикт всё равно неверный.

Максимум с нуля. Разобран выше. Проверяется одним тестом: подайте только отрицательные числа.

Как проверять себя

У задач этого занятия есть три теста, которые ловят почти всё:

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

Прогоняйте их до отправки, а не после первого неверного вердикта.

теория

Практика: пятнадцать задач

Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.

Почти во всех задачах входные данные устроены одинаково: сначала количество чисел, затем сами числа по одному в строке. Разбирать этот кусок заново каждый раз не нужно — он один и тот же.

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

лестница задач
развёрнутый ответ

Две ошибки в одной программе

Задача: «дано nn чисел, выведите их сумму и наибольшее из них».

Ученик написал:

n = int(input())

for _ in range(n):
    s = 0
    best = 0
    x = int(input())
    s += x
    if x > best:
        best = x

print(s, best)

Программа почти всегда отвечает неверно. Найдите обе ошибки, объясните, что именно она выведет на числах 4, −7, 2, и как её исправить.

Войдите, чтобы ответить.