Лекция №1. Олимпиадные задачи по программированию

Хорошевич Павел Александрович

Дорожная карта

Темы

Темы

Олимпиадные задачи по программированию

Решите известную задачу из области информатики за кратчайшее время.

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

Решить олимпиадную задачу - значит активизировать все свои познания в области информатики для получения кода программы, способного пройти тесты, подготовленные авторами задачи.

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

Решение задач - не самоцель.

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

Структура олимпиадной задачи

Зачастую олимпиадная задача по программирования состоит из следующих частей:

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

  • Описание входных и выходных данных - формат ввода/вывода, ограничения.

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

Рекомендации по подготовке к олимпиадам

  • Практика
  • Классификация задач
  • Синтаксис языка
  • Тестирование кода
  • Командная работа
  • Быстрая печать

Практика решения задач

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

Используйте архивы задач с автоматической проверкой отправленных решений:

Классификация задач

Нужно уделить время развитию навыка классификации задачи по методу её решения.

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

Синтаксис языка

Между интерпретируемыми (Python, JavaScript) и компилируемыми языками (C++, C#, Pascal) отдавайте предпочтение компилируемым.

Подробнее остановитесь на доступных типах данных и их ограничениях.

Рассмотрите синтаксические возможности языка, которые позволяют писать более лаконичный код - “синтаксический сахар”.

Уделите внимание изучению встроенных модулей - работа с датой и временем, обработка строк, математические вычисления и так далее.

Тестирование кода

Важно развивать навык тестирования и отладки кода.

Rubber Duck Debugging

Rubber Duck Debugging

Проработайте с учащимися типовые синтаксические и семантические ошибки и методы их исправления:

  • трассировка;
  • отладочные сообщения;
  • метод “ограды”.

Командная работа

Зачастую, в командных соревнованиях, на одну команду выделяется один компьютер.

  • Написание псевдокода/кода на бумаге, пока компьютер занят другим участником.
  • Отладка без компьютера: рассмотреть типовые ошибки, практика трассировки кода.
  • Тестирование кода: заранее подготовить пограничные тестовые случаи.
  • Парное программирование.

Быстрая печать

Скорость набора кода программы может стать одним из факторов, влияющих на количество заработанных баллов.

Рекомендуйте учащимся использовать клавиатурные тренажёры.

Особое внимание уделить специальным символам: (), [], <>, ;, :, "" и так далее.

Сервисы для измерения скорости набора:

solo.nabiraem.ru/ru/overview/typingtest

Методы решения задач

Метод “Грубой силы” (Bruteforce)

Метод решения задачи основывается непосредственно на формулировке условия. При необходимости варианты решения находятся простым перебором.

  • Можно применять почти для любого класса задач.
  • Для многих сложных задач (сортировка, поиск, поиск подстрок) даёт вполне рациональные алгоритмы.
  • Метод может оказаться полезен для решения подзадач более крупной задачи.

Задача

На множестве из n точек на плоскости найти две, расположенные друг к другу ближе других.

Для двух точек \(P_i = (x_i, y_i)\) и \(P_j = (x_j, y_j)\): \[ d(P_i,P_j)=\sqrt{(x_i-x_j)^2+(y_i-y_j)^2} \]

  • Переберём все возможные пары точек для поиска пары с наименьшим расстоянием.
  • Для оптимизации можем сравнивать только пары точек \((P_i, P_j)\) для которых \(i < j\).

Код решения

static double dist(double x1, double y1, double x2, double y2)
{
    return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y1, 2));
}

static void Main(string[] args)
{
    const int N = 3; // количество точек
    double[] x = new double[N];
    double[] y = new double[N];

    for (int i = 0; i < N; i++)
    {
        double xt = Double.Parse(Console.ReadLine());
        double yt = Double.Parse(Console.ReadLine());
        x[i] = xt;
        y[i] = yt;
    }

    double dMin = 1E300;
    int ind1 = -1;
    int ind2 = -1;

    for (int i = 0; i < N - 1; i++)
    {
        for (int j = i + 1; j < N; j++)
        {
            double d = dist(x[i], y[i], x[j], y[j]);
            if (d < dMin)
            {
                dMin = d;
                ind1 = i;
                ind2 = j;
            }
        }
    }

    Console.WriteLine("Первая точка: {0},{1}", x[ind1], y[ind1]);
    Console.WriteLine("Вторая точка: {0},{1}", x[ind2], y[ind2]);
}

Метод “Разделяй и властвуй”

  1. Экземпляр задачи разбивается на несколько меньших экземпляров той же задачи, в идеале - одинакового размера.

  2. Решаются меньшие экземпляры задачи (обычно рекурсивно, хотя иногда для небольших экземпляров применяется какой-нибудь другой алгоритм).

  3. При необходимости решение исходной задачи находится путем комбинации решений меньших экземпляров.

Примеры задач и алгоритмов

  • Бинарный поиск
  • Сортировка слиянием
  • Быстрая сортировка
  • Алгоритм Штрассена (умножение матриц)

Задача

Дан массив из \(n\) чисел. Не используя циклы найдите максимальный элемент массива.

Массив

Массив

Код решения

static int PoiskMax(int[] a, int lev, int prav)
{
    if (lev == prav)
        return a[lev];
    else
    {
        int sred = (lev + prav) / 2;
        int levMax = PoiskMax(a, lev, sred);
        int pravMax = PoiskMax(a, sred + 1, prav);

        return Math.Max(levMax, pravMax);
    }
}

static void Main(string[] args)
{
    const int N = 10; // количество точек
    int[] x = new int[N];

    for (int i = 0; i < x.Length; i++)
    {
        x[i] = int.Parse(Console.ReadLine());
    }

    int max = PoiskMax(x, 0, N - 1);

    Console.WriteLine("Максимальное значение: {0}", max);
}

“Жадные” алгоритмы

На каждом шаге алгоритм выбирает наилучшую доступную альтернативу в надежде, что последовательность таких выборов приведёт к оптимальному решению задачи.

  1. Локальная оптимальность. Алгоритм делает выбор, который кажется наилучшим в текущий момент времени.
  2. Бесповоротность. Принятые решения не пересматриваются, что означает, что алгоритм не возвращается назад для изменения предыдущих выборов.
  3. Постепенное построение решения. Решение формируется поэтапно, добавляя элементы к уже имеющемуся частичному решению.

Примеры задач и алгоритмов

  • Задача о рюкзаке. Выбор предметов с максимальной ценностью при ограничении по весу. Здесь жадный подход может включать выбор предметов с наибольшей ценностью на единицу веса.
  • Задача о размене монет: Минимизация количества монет для сдачи заданной суммы путем выбора монет с наибольшим номиналом.

Задача

У вас есть неограниченное количество монет номиналов 1, 5, 10 и 25. Вам нужно составить сумму \(S\) с использованием наименьшего количества монет.

Код решения

static void Main(string[] args)
{
    int[] coins = { 100, 50, 20, 10, 5, 1 };

    int S = int.Parse(Console.ReadLine());
    int i = 0;

    while (S > 0 && i < coins.Length)
    {
        while(S >= coins[i])
        {
            S = S - coins[i];
            Console.WriteLine(coins[i]);
        }
        i += 1; // переход к следующему номиналу
    }
    if (S != 0)
        Console.WriteLine("Невозможно разменять сумму {0}", S);
}

Динамическое программирование

Динамическое программирование - метод решения задач оптимизации и подсчёта.

Суть метода - сохранение промежуточных вычислений для их повторного использования. Промежуточные решения являются решением подзадач.

Подзадача - задача с меньшим количеством параметров или меньшими значениями параметров.

Решения подзадач сохраняются в одномерных или двухмерных таблицах (массивах или матрицах).

Примеры задач и алгоритмов

  • Задача о рюкзаке: Определяет, какие предметы следует взять с собой в рюкзак ограниченной вместимости, чтобы максимизировать общую ценность.

  • Задача о самой длинной возрастающей подпоследовательности (LIS): Определяет самую длинную подпоследовательность, которая строго возрастает.

  • Задача о размене монет: Определяет количество способов разменять заданную сумму денег с использованием определенных номиналов монет.

Задача

Вычислите \(n\)-ое число Фибоначчи. \[ F(0)=0; F(1)=1; F(n)=F(n-1)+F(n-2) \]

Код решения

static void Main(string[] args)
{
    int N = int.Parse(Console.ReadLine());

    int[] F = new int[N + 2];
    
    F[0] = 0;
    F[1] = 1;

    for (int i = 0; i < N; i++)
    {
        F[i + 1] += F[i];
        F[i + 2] += F[i];
    }

    Console.WriteLine(F[N-1]);
}

Этапы решения задач по информатике

1. Содержательная постановка задачи

Обычно в условии есть «литературное введение», придающее задаче сюжет. Во введении может, прямо или косвенно, содержаться важная информация, касающаяся условия.

На этом этапе целесообразно построить схему, рисунок, который облегчит понимание условия задачи и поиск ее решения.

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

2. Формализация условия задачи

Построение математической модели исходной задачной ситуации, лежащей в основе ее разрешения.

Построить математическую модель – значит достаточно формально и математически строго понять условие.

Выделяют в явном виде предположения, на которых будет основываться математическая модель; фиксируют соотношения, связывающие результаты с исходными данными.

3. Построение алгоритма решения задачи

Разработка алгоритма, которая включает в себя:

  • выбор метода проектирования алгоритма;

  • выбор формы записи алгоритма (блок-схемы, псевдокод и др.);

  • проектирование самого алгоритма.

4. Программная реализация

Собственно написание программы.

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

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

Запись и ввод программы на выбранном языке программирования.

5. Отладка и тестирование программы

Тестирование алгоритма – это проверка правильности или неправильности работы алгоритма на специально заданных тестах или тестовых примерах – задачах с известными входными данными и результатами (иногда достаточны их приближения).

Тестовый набор должен быть минимальным и полным, то есть обеспечивающим проверку каждого отдельного типа наборов входных данных, особенно исключительных случаев.

Тестирование программы (алгоритма)

Грамотный подбор тестов и полное тестирование может дать некоторую картину работоспособности (неработоспособности) программы.

Тестирование алгоритма не может дать полной, 100%-ой гарантии правильности алгоритма для всех возможных наборов входных данных, особенно для достаточно сложных алгоритмов.

Трассировка –

это метод пошаговой фиксации динамического состояния алгоритма на некотором тесте.

Часто осуществляется с помощью трассировочных таблиц, в которых каждая строка соответствует определённому состоянию алгоритма, а столбец – определённому состоянию параметров алгоритма (входных, выходных и промежуточных).

Трассировка облегчает отладку и понимание алгоритма.

Отладка программы –

это процесс поиска и исправления ошибок в алгоритме, явных или неявных.

Тестирование и отладка включают в себя:

  • синтаксическую отладку;

  • отладку семантики и логической структуры программы;

  • тестовые расчеты и анализ результатов тестирования;

  • попутное совершенствование программы.

Этапы процесса тестирования

  1. Проверка в нормальных условиях

  2. Проверка в экстремальных условиях

  3. Проверка в исключительных ситуациях.

Вопросы

Что произойдет, если программе, не рассчитанной на обработку отрицательных и нулевых значений переменных, в результате какой-либо ошибки придется иметь дело как раз с такими данными?

Как будет вести себя программа, работающая с массивами, если количество их элементов превысит величину, указанную в объявлении массива?

Что произойдет, если числа будут слишком малыми или слишком большими?

6. Отправка решения на проверку

Отправка решения на проверку, проведение расчетов, анализ и проверка полученных результатов.

7. Обобщение решения

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

Этапы решения задач

Этапы решения задач