Все статьи » ЗФТШ Информатика

Статьи , страница 4

  • §4. Кодирование звуковой информации
    Что нужно знать
    • при оцифровке звука в памяти запоминаются только отдельные значения сигнала, который нужно выдать на динамик или наушники
    • частота дискретизации определяет количество отсчетов, запоминаемых за `1` секунду; `1` Гц (один герц) – это один отсчет в секунду, а `8` кГц – это `8000` отсчетов в секунду
    • глубина кодирования – это количество бит, которые выделяются на один отсчет
    • для хранения информации о звуке длительностью `t` секунд, закодированном с частотой дискретизации `f` Гц и глубиной кодирования `B` бит требуется `B*f*t` бит памяти; например, при `f=8` кГц, глубине кодирования `16` бит на отсчёт и длительности звука  `128` секунд требуется

      `I=8000*16*128=1384000` бит

      `I=8000*16*128//8=2048000` байт

      `I=8000*16*128//8//1024=2000` Кбайт

      `I=8000*16*128//8//1024//1024~~1,95` Мбайт


    • при двухканальной записи (стерео) объем памяти, необходимый для хранения данных одного канала, умножается на `2`, при четырехканальной(квадро) – умножается на `4`
    • для упрощения ручных расчетов можно использовать приближённые равенства

    `1` мин  `= 60` сек `~~64` сек `= 2^6` сек

    `1000~~1024=2^(10)`

    Итак, объём музыкального файла вычисляется по формуле

    `I=f*r*k*t`,

    где `f` – частота дискретизации,  `r`  – разрешение (глубина кодирования), `k`  – количество каналов, `t` – время звучания.

  • §5. Символьный тип данных в языке Паскаль

    Теперь применим полученные знания о представлении текстовой информации на практике. В языке программирования Паскаль для работы с текстовой информацией есть специальный символьный тип переменных, который называется char (от английского character). Переменные этого типа занимают в оперативной памяти по `1` байту и, соответственно, могут принимать `256` различных значений. Значениями переменных этого типа являются элементы какой-либо однобайтовой кодовой таблицы (например, KOI-`8` или Windows-`1251`). Какие именно символы являются значениями данного типа, зависит от того, какая кодовая таблица используется в момент выполнения (а не написания) программы. То есть одна и та же программа, например, печатающая изображение всех символов кодовой таблицы, на компьютерах с различными текущими кодировками будет иметь различные результаты работы.


    Переменным символьного типа можно присваивать значения при помощи оператора присваивания. При этом есть два способа записи символьных констант. Первый способ – записать явное изображение символа, заключив его в апострофы. Пусть, например, переменная C имеет тип char. Присвоим ей значение: C:= 'a'; Описанный способ записи символьных значений удобно применять практически всегда. Единственный недостаток этого способа заключается в том, что так невозможно представить служебные символы, которые не имеют явных изображений (в кодовой таблице это первые `32` символа). Поэтому существует ещё один способ записи символьных констант – сначала указать спецсимвол решётку (#), а потом код интересующего нас символа. Например, C:=#13; Недостаток этого способа заключается в том, что нужно помнить коды всех символов, поэтому обычно его применяют только для записи символов без явного изображения.


    Переменные типа char можно выводить на экран при помощи оператора вывода и вводить с клавиатуры. Апострофы при вводе набирать не нужно (каждый апостроф также будет считаться отдельным символом). Служебные символы вводятся следующим образом: нужно зажать alt и на правой цифровой клавиатуре набрать код символа (например, 13).


    К переменным типа char можно применять операции сравнения (> , < , >= , <= , = , <>). При этом сравниваются коды символов и большим признаётся символ, имеющий больший код (то есть символ, находящийся дальше от нулевого). Результатом операции сравнения является логическое значение – true или false.


    Существует `5` стандартных функций для работы с переменными символьного типа:

    Функция

    Действие

    Тип

    аргумента

    Тип

    результата

    Ord(c)

    Выдаёт код символа

    Char

    Integer

    Chr(x)

    Выдаёт символ по коду

    Integer

    Char

    Succ(c)

    Выдаёт следующий символ кодовой таблицы. Не определена для последнего символа

    Char

    Char

    Pred(c)

    Выдаёт предыдущий символ кодовой таблицы. Не определена для нулевого символа

    Char

    Char

    Upcase(c)

    Если аргумент является строчной латинской буквой, превращает его в соответствующую заглавную. Иначе ничего не делает

    Char

    Char


    Тип char является порядковым, то есть для каждого символа можно назвать его порядковый номер в типе, а также следующий и предшествующий элементы типа. Например, символ '1' имеет код `49`, следующий символ – это '2', а предыдущий – '0'. Благодаря этому свойству переменные типа char могут использоваться в качестве счётчиков в цикле for. Например, распечатать все заглавные латинские буквы можно следующим образом:


    For  c:= 'A' to 'Z' do write (c);


    где переменная c имеет тип char.


    Если в цикле for используется слово to, то на каждом шаге цикла счётчик будет принимать следующее значение в типе, в случае же downto – предыдущее значение в типе.


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


    Задача 1

    Вывести на экран все символы кодовой таблицы.

    Решение

    Эту задачу можно решать двумя способами: перебрать все символы или все их коды – разница только в типе счётчика цикла.

    Способ 1:

      var c:char;

      begin

         for c:=#0 to #255 do

            write(ord(c),'-',c,' ');

          readln

    end.

    Способ 2:

    var i:integer;

    begin

       for i:=0 to 255 do

          write(i, '-',chr(i), ' ');           

       readln

    end.



    Задача 2

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

    Решение

    Эта задача демонстрирует очень важную вещь – как превратить символ-цифру в целое число. Это осуществляется следующим образом: необходимо вычислить код интересующего нас символа (например, код единицы `49`) и вычесть из него код символа «ноль». В любой кодировочной таблице символы-цифры идут подряд, поэтому, выполнив указанные действия, мы гарантированно получим числовое значение символа-цифры. Приведём полный текст решения.

    var c: char; s: integer;

    begin

       s :=0;

       read (c);

       while c <> '.' do

         begin

         if (c >= '0')and(c <= '9')

           then s:= s+ord(c)–ord('0');

             read (c);

           end;

       writeln ('s=',s);

       readln

      end.


    Задача 3

    Дана непустая последовательность слов, состоящих из заглавных и строчных латинских букв в любом порядке. Между соседними словами запятая, за последним словом – точка. Никакие другие символы в последовательность не входят. Определить количество слов, которые начинаются на букву `Z`.

    Решение

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

    var c:char; s:integer;

    begin

       s:=0;

       repeat

         read(c);

         if c='Z' then s:=s+1;

         repeat

           read(c)

         until (c=',')or(c='.') 

       until c='.'; 

       writeln('s=',s);

       readln

    end.






     

  • §6. Оператор выбора Case

    Данный оператор представляет собой естественное расширение условного оператора. В общем виде он записывается следующим образом:

    case <выражение порядкового типа> of

      константа_1: оператор_1;

      константа_2: оператор_2;

            ...

      Константа_n: оператор_n;

      else оператор

    end

    Слова: case, of, else, end -  являются ключевыми словами языка. Выражение, стоящее между словами case и of, называется селектором и должно иметь порядковый тип. Тип является порядковым, если можно для каждого значения назвать порядковый номер в типе, предыдущее и следующее значение в типе (кроме первого и последнего значения в типе). Из известных нам стандартных типов порядковыми являются типы integer, longint, boolean и char. Тип real порядковым не является.

    Работает оператор выбора следующим образом. Сначала вычисляется значение селектора, затем оно сравнивается с константами. В случае совпадения селектора с какой-нибудь константой выполняется оператор, стоящий после этой константы, далее управление переходит на следующий за case оператор программы. Если селектор не совпал ни с одной из констант, то выполняется оператор после слова else. Очевидно, что селектор и константы должны иметь одинаковые типы. Иначе невозможно будет провести операции сравнения.

    Если нужно для многих различных значений селектора выполнить один и тот же набор команд, то можно не записывать множество строк с одинаковой правой частью, а перечислить константы через запятую, затем поставить двоеточие и один раз написать нужную последовательность команд. Если константы идут подряд, можно также записать их в виде диапазона: константа_1..константа_2. В этом случае команда будет выполняться при совпадении селектора с любой константой из диапазона. Граничные значения считаются включёнными в диапазон. Можно также указать несколько диапазонов через запятую.

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

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

    Пример 1

    case c of

      '+': x := x + y;

      '-': x := x - y;

      '*': x := x * y;

      else writeln('error')

     end;

    Пример 2

    case c of

      'a'..'z','A'..'Z': writeln('letter');

      '0'..'9':          writeln('digit')

     end;

       

  • Элементы теории математических игр
    Игрой

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

    Согласно этому определению, довольно много жизненных ситуаций можно считать играми - для этого требуется лишь борьба двух или более лиц и какие-либо интересы, за которые эти лица ведут борьбу. Шахматы, домино, прыжки в высоту - всё это игры. Стремление занять свободное место в автобусе, соперничество мировых держав в ядерной сфере, беседа сотрудника ГИБДД с нарушителем, поход семейной пары в торговый центр - и это тоже игры. Так, в случае стремления занять свободное место в пустом автобусе в этом процессе участвуют не менее двух человек, которые ведут борьбу за свободные места (свои интересы), причём довольно часто количество свободных мест намного меньше количества участвующих в этой игре человек, поэтому в этой игре есть выигравшие и проигравшие. В этом случае интересы (занять свободное место) у игроков совпадают. Однако в случае игры «поход семейной пары в торговый центр» интересы часто строго противоположные: жене хочется совершить как можно больше покупок; мужу - потратить как можно меньше денег на эти покупки.

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

    Пример 1

    Для решения спора Петя и Вася обращаются к компьютеру за случайным натуральным числом. Если выданное число - чётное, спор выигрывает Петя, если нечетное - спор выигрывает Вася. Является ли описанная процедура игрой?

    Решение

    Данная процедура тоже является игрой - два игрока ведут борьбу за свои интересы (выиграть спор), и то, как это они делают - неважно. Фактически, игроки с помощью компьютера реализовали подкидывание монетки.


    Пример 2

    Для решения спора Петя и Вася пишут цифры по очереди на доске слева направо, начинает Петя. Если после десяти ходов полученное `10`-значное число не делится на девять, в споре побеждает Петя, а если делится – Вася. Докажите, что Вася может выиграть спор.

    Решение

    Второй игрок (Вася) может дополнять число, написанное первым игроком, до девяти. Если ход Пети - «`9`», то ход Васи - «`0`» и т. п. После десяти ходов получим `10`-значное число, сумма цифр которого равна `9^(**)5=45`, и полученное число будет делиться на девять. Таким образом, второй игрок (Вася) сможет выиграть при любых ходах первого игрока (Пети).


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



  • § 1. Математические игры

    Будем называть игру математической, если для неё выполнены следующие условия:

    Условия Математической игры

    Условие 1. В игре участвуют два игрока.

    Условие 2. Игра заканчиваются выигрышем одного из участников. Это автоматически означает проигрыш соперника. Иногда в математических играх допускают ничью.

    Условие 3. В игре участники ходят по очереди и помнят все предыдущие ходы.

    Условие 4. Игра характеризуется позицией, которая зависит только от ходов игроков.

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

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

    Пример 3

    Для решения спора Петя и Вася пишут на листочках по натуральному числу. Если сумма написанных чисел - чётная, спор выигрывает Петя, если нечетная - спор выигрывает Вася. Является ли описанная процедура математической игрой?

    Решение

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

    Сделаем небольшую модификацию условий игры, чтобы игра стала математической и посмотрим, какая игра из этого получится. Чтобы условие 3 поочередности выполнялось, сначала должен походить первый игрок, написать своё число на бумажке и показать это число всем, включая второго игрока. Кто из двух игроков будет первым, они между собой должны договориться сами. И тогда уже второй игрок, зная число, которое написал первый, должен написать своё число, затем эти два числа будут сложены и сумма проверена на чётность.

    Однако, если второй игрок обладает хоть каким-либо интеллектом, он может подобрать своё число, чтобы сумма была выигрышной для него чётности. Суть «подкидывания монетки» от этого полностью теряется, т. к. данная игра находится под полным контролем второго игрока.

    Пример 4

    Два человека встречаются и обмениваются закрытыми сумками, понимая, что одна из них содержит деньги, другая - товар. Каждый игрок может уважать сделку и положить в сумку то, о чём договорились, либо обмануть партнёра, дав пустую сумку. Является ли эта игра математической?

    Решение

    Во-первых, эта игра не удовлетворяет условию 2: в условии не определено, какой игрок выигрывает в каком случае, а какой автоматически при этом проигрывает. Во-вторых, игроки ходят одновременно, а не по очереди, что нарушает условие 3. Поэтому данная игра не является математической.

    Заметим, что условие 2 можно выполнить, считая, что в случае если один игрок обманул другого, обманувший игрок выиграл, а обманутый проиграл, в остальных случаях (оба игрока честные или оба обманщики) зафиксировать ничью. Однако условие 3, как и в предыдущем примере, уже нельзя выполнить без существенного изменения самой игры.

    Итак, в математической игре имеются два игрока, которые ходят поочередно. Участник, который начинает игру, обычно называется первым игроком, его соперник – вторым. Имеется конечное или бесконечное множество позиций. В каждой позиции для обоих игроков указаны допустимые ходы – разрешённые переходы в другие позиции. Некоторые позиции объявляются выигрышными для какого-то игрока, что автоматически означает, что эти позиции являются проигрышными для соперника. Очень часто выигрышными объявляются  те  и  только  те  позиции,  из  которых соперник не может сделать ход, т. е. выигрывает тот игрок, которому удаётся своим последним ходом достичь позиции, в которой у соперника нет допустимых ходов.

    Пример 5 «Ним». 

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

    Решение

    Позицией в данной игре являются два числа `(x, y):` `x` – количество камней в первой куче, `y` – количество  камней во второй куче. Игрок выигрывает, если противник не может сделать ход, т. е. перед ходом противника камней в обеих кучах не останется. Таким образом, позиция `(0, 0)` является выигрышной для того из игроков, который попал туда своим последним ходом.

    Особенно отметим следующее.

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

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

    Поэтому позиция должна ещё характеризоваться номером игрока (либо того, который пришел в эту позицию, либо того, который делает ход из этой позиции в зависимости от ситуации). Так, если в примере 7 добавить номер игрока, который делает ход, то теперь позиция в этой задаче будет выражаться тремя числами `(x,y,n)`, где `n` – номер игрока, который делает ход, имея в начале $$ x$$ камней в первой куче, а $$ y$$ – во второй.

    Позиция `(0,0,1)` будет проигрышной для первого игрока (он не может сделать ход) и выигрышной для второго, позиция `(0,0,2)` – наоборот.

    Однако в играх, в которых игроки преследуют одинаковые цели и возможные ходы у обоих игроков одинаковы, как например, в примере 5, можно номер игрока из позиции опустить. В этом задании мы будем рассматривать только такие игры.

    Пример 6

    В точке 0 оси координат находится фишка. За ход игрок обязан подвинуть фишку на единицу влево или вправо. Выиграет тот игрок, после хода которого координата фишки превысит десять.  Как определить позиции в данной игре? Какие позиции следует объявить выигрышными? Какие позиции следует объявить ничейными?

    Решение

    Позицией является целое число `(x):` положение фишки на оси. При этом все позиции с `x > 10` будут проигрышными для первого игрока, т. е., выигрышными для второго. Стартуя из позиции `(10)`, первый игрок может одним ходом передвинуть фишку в позицию `(11)` и выиграть. Если же игра начинается из позиции `(x)`, `[x < 10]`, то ни первый, ни второй игрок не могут гарантированно рассчитывать на победу, так как любой игрок в данной игре может не позволить своему противнику достичь выигрышной позиции, просто двигая каждый раз своим ходом фишку влево. Поэтому, стартуя из позиции `(x)`, `[x < 10]`, игра может закончиться выигрышем одного из игроков, если и только если соперник ошибётся. Но что следует считать исходом игры при старте, например, из начала координат (как в условии примера)? Можно было бы, например, считать, что исход игры при старте из начала координат просто не определён. Но мы потребуем выполнения более жёсткого условия.

    Дополнительное условие математических игр.

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

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

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

    Для того, чтобы игра из примера 6 удовлетворяла условию 5, нужно кроме уже  заданных  выигрышных  позиций  `(x)`, `[x > 9]`  объявить   все  позиции  `(x)`, `[x < 10]` ничейными[1].

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


    [1] Таким образом, в примере 6 при старте из любой точки кроме точки `(10)` игроки не сделают ни одного хода, и немедленно будет объявлен результат.

  • § 2. Стратегия. Правильная игра

    Вернёмся к примеру 5 и зададимся вопросом: кто выиграет?

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

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

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

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

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

    Теорема

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

    Идея доказательства этого утверждения в частном случае будет рассмотрена при решении задач методом  анализа с конца (см. § 3).

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

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

    Правильной

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

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

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

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

  • 3.1. Удачный ход

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

    Пример 7

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

    Решение

    Выиграет второй игрок. Для этого мысленно разрежем шахматную доску пополам линией, параллельной одной из сторон доски. Второй игрок должен ставить слона на место, симметричное полю, на которое текущим ходом поставил свою ладью первый игрок относительно проведённой оси. Докажем от противного, что второй игрок всегда сможет сделать ход.

    Пусть это неверно и второй игрок не сможет сделать хода. Разберём два случая.

    Случай 1. На поле предполагаемого хода уже стоит слон. Но этот слон не мог быть поставлен ранее вторым игроком, так как он ставит слонов только симметрично ходам первого игрока. Если первый игрок ранее поставил  слона на это поле, то второй игрок был обязан своим ходом поставить слона на поле, симметричное полю противника. Однако по условию на это поле слона поставил первый игрок текущим ходом. Получаем противоречие.

    Случай 2. Данное поле находится под боем какого-то слона. Заметим, что этот слон не был поставлен первым игроком на предыдущем ходу, так как два симметричных относительно оси слона не бьют друг друга. Тогда, в соответствии со стратегией второго игрока, слон, расположенный симметрично данному, также должен уже стоять на доске. Однако этот слон будет бить слона, поставленного первым игроком предыдущим ходом. Противоречие.

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

    Пример 8

    В кучке лежат: а) `30` камней; б) `32` камня. За ход можно взять от одного до пяти камней из кучи. Проигрывает тот, кто не сможет сделать ход. Кто выигрывает при правильной игре?

    Решение

    В данном случае работает стратегия дополнения до шести. Пусть своим ходом первый игрок берёт `x in{1,2,3,4,5}` камней. Тогда в пункте а) второй игрок отвечает ходом `(6-x)`, и поскольку после каждого его хода количество камней будет делиться на шесть,  то в итоге второй игрок выиграет.

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

    Пример 9

    Два игрока перемещают ладью из левого нижнего угла `("a"1)` шахматной доски в правый верхний `("h"8)`. За ход можно сместить ладью на любое количество клеток вверх или вправо. Кто выиграет при правильной игре?

    Решение

    Выиграет второй игрок. Для этого ему нужно во время ходов возвращать ладью на диагональ, проведенную из левого нижнего угла в верхний правый угол. Подумайте, почему первый игрок проиграет при любых своих ходах.



  • 3.2. Анализ с конца

    Вторым важным способом решения задач является решение задачи с конца. Предположим (хотя это и не всегда верно), что для обоих игроков одни и те же позиции являются выигрышными.

    Вернёмся к примеру 9.

    Для нахождения выигрышной стратегии рассмотрим общую задачу. Считаем, что начальная позиция является параметром, и будем искать выигрышную стратегию при старте с этой позиции. Будем обозначать знаком «`-`» позиции, в которых при правильной игре участник, начинающий играть из данной позиции, выиграет, и знаком «`+`» отметим позиции, ведущие к поражению[1].

    Если игра начинается в поле `"h"8`, первый игрок уже проиграл – это позиция «`+`» (рис. 1).

    Далее, если игра стартует с полей `"h"1-"h"7` или `"a"8-"g"8`, то начинающий игрок может за один ход достичь поля `"h"8` и выиграть. Это позиция «`-`» (рис. 2).

    Рассмотрим ладью, стоящую в поле `"g"7`. У первого игрока есть только два хода – `"g"8` и `"h"7`. Но в обеих этих позициях стоит «`-`». Следовательно, второй игрок, стартующий из этих позиций, выиграет. Как бы ни ходил первый игрок, он проиграет. Это снова позиция «`+`».

    Далее, рассмотрим группы полей `"g"1-"g"6` и `"a"7-"f"7` (рис. 3). Стартуя из этих полей, первый игрок может за один ход попасть в поле `"g"7`, которое помечено знаком «`+`». Любой ход второго игрока из `"g"7` ведёт к его проигрышу.

    Продолжая таким образом заполнять шахматную доску, мы видим, что знаки «`+`» размещаются на диагонали `"a"1-"h"8` (рис. 4). В поле a1 стоит знак «`+`», поэтому первый игрок потерпит поражение.

    Зафиксируем общие правила расстановки знаков «`+`» и «`-`»:

    правила расстановки знаков «`+`» и «`-`»:

    1) знаком «`-`»  обозначаются позиции, в которых при правильной игре участник, стартующий из данной позиции, выиграет, и знаком «`+`» отмечаются позиции, ведущие к поражению;

    2) знак «`-`»  ставится в позиции, из которой можно за один ход прийти в позицию со знаком «`+`»;

    3) знак «`+`» ставится в выигрышных позициях, а также в тех позициях, из которых все возможные ходы ведут только в позиции, уже отмеченные знаком «`-`»[2].

    Таким образом, сначала нужно расставить знаки «`+`» в выигрышных позициях. На втором этапе нужно отметить знаком «`-`» те позиции, которые отделяет от выигрышных один ход. На третьем этапе следует просмотреть все позиции и найти «тупиковые», ведущие к положениям, обозначенным знаком «`-`». На игровом поле обязательно будет хотя бы одна такая позиция[3]. Второй и третий этапы необходимо поочередно повторять до тех пор, пока начальная позиция не будет помечена знаком «`+`» или «`-`», что и даст ответ на вопрос, кто выиграет при правильной игре.

    Как же должен действовать побеждающий участник игры? Он должен стремиться ходить в позиции, отмеченные знаком «`+`». При этом после очередного хода соперника он опять окажется в позиции со знаком «`-`», так как по определению знака «`+`» все возможные ходы из этой позиции ведут только в позиции со знаком «`-`». Таким образом, стратегия выигрывающего игрока формулируется просто: делать ход в позиции, обозначенные знаком «`+`». По определению знака «`-`» из  этой позиции существует хотя бы один ход в позицию, отмеченную знаком «`+`», поэтому такой ход у выигрывающего игрока всегда будет в наличии.

    Отметим следующий факт. Если известно, что игра длится не более чем `n` ходов при любых действиях первого и второго игроков, то начальная позиция обязательно будет помечена не более чем за `n` повторений шагов `2` и `3`. Это является идеей доказательства основной теоремы из § 2 в частном случае игр, в которых ничейных позиций нет, и каждая позиция является выигрышной для одного из игроков.

    Пример 10

    Два игрока играют в следующую игру. Перед ними лежат две кучи камней, в первой – три камня, а во второй – два камня. У каждого игрока имеется неограниченно много камней. Игроки ходят по очереди. Ход состоит в том, что игрок или увеличивает в три раза число камней в какой-либо куче, или добавляет один камень в любую кучу. Выигрывает тот игрок, после хода которого, в двух кучах станет не менее `16` камней. Кто выиграет при правильной игре: игрок, сделавший первый ход, или игрок, сделавший второй ход? Каким должен быть первый ход выигрывающего игрока? Ответ обоснуйте.

    Решение

    Попробуем изобразить позиции графически. Рассмотрим таблицу, в которой количество камней в первой куче будет соответствовать номеру столбца, а количество камней во второй куче – номеру строки. Чёрным цветом выделена позиция `(2, 3)`, с которой должна начинаться игра в условии:

    1. Выигрышные позиции – точки с координатами `x`, `y`, где `x + y ≥ 16`. Данные точки обозначим знаком «`+`» в таблице ниже[4].

    2. Далее, ставим знак «`-`» в позиции, которые отделяет от выигрышных один ход.

    По условию, можно либо увеличить одну из кучек в три раза, либо добавить  камень в одну из  куч, т. е.  мы  должны  поставить знак «`-`» в позицию `(x, y)`, если верно одно из условий: `x+y+1≥16`; `x+3y≥16`; `y+3x≥16`.

    3. После чего, ставим знак «`+`» в те позиции, из которых все ходы ведут только в позиции, обозначенные знаком «`-`». Таковыми будут позиции `(0, 5)`, `(5, 0)` и `(4, 3)`, `(3, 4)`.

    4. Знак «`-`» ставим в те позиции, стартуя из которых можно за один ход дойти до одной из позиций, отмеченных знаков «`+`» (поставленных на этапе 3).

    Стартуя из позиций `(4, 0)`, `(0, 4)`, `(3, 3)`, `(2, 4)`, `(4, 2)`, можно попасть в позиции, обозначенные знаком «`+`», увеличив количество камней в одной из кучек на единицу. Из позиций `(1, 4)` и `(4, 1)` можно прийти в позиции со знаком «`+`», увеличив в три раза количество камней в меньшей куче.

    5. Знак «`+`» ставим в те позиции, из которых все ходы ведут только в позиции, обозначенные знаком «`-`». На этот раз таковыми будут позиции `(2, 3)` и `(3, 2)`.

    В позиции `(2, 3)` был поставлен знак «`+`», а это значит, что победит второй игрок.

    При оформлении задачи необходимо указать выигрывающего игрока, записать его стратегию и показать, что этот игрок победит при любых ответах соперника. Если имеется таблица позиций, то стратегия выигрывающего игрока формулируется простым правилом: делать ходы в позиции, отмеченные знаком «`+`». Но эту стратегию рекомендуется записать в явном виде. Таблицу позиций же, наоборот, при оформлении работы можно не рисовать (она уже сделала свое дело: помогла определить победителя и найти его стратегию).

    Образец оформления примера 10.

    Покажем, что второй игрок может выиграть при произвольных ответах первого игрока.

    Рассмотрим все возможные начальные ходы первого игрока и укажем правильные ответы соперника:

    а) если первый игрок в три раза увеличивает число камней в одной из куч, то второй игрок должен увеличить количество камней в этой же куче также в три раза. Тогда в обеих кучах будет как минимум 2*3*3+3=21 камень. Второй игрок побеждает. Рассмотрение этого случая закончено;

    б) если  первый  игрок из позиции (2, 3) делает ход (2, 4) или (3, 3), то второй игрок должен пойти в позицию (3, 4) (именно она в нашем случае обозначена знаком «+»). Теперь первый игрок делает второй ход (заметим, этот ход не может быть выигрышным). Возможны три варианта:

    - первый игрок увеличивает в три раза количество камней в одной из куч. Тогда второй игрок повторяет это действие с оставшейся кучкой камней, получает в сумме 21 камень и выигрывает,


    - первый игрок добавляет один камень в первую кучу – позиция (4, 4). Тогда второй игрок увеличивает количество камней в одной из куч в три раза, получает в сумме 16 камней и выигрывает,

    - первый игрок добавляет один камень во вторую кучу – позиция (3, 5). Тогда второй игрок увеличивает количество камней во второй куче в три раза, получает в сумме 18 камней и выигрывает.


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


    Обратите внимание, что стратегию второго игрока можно придумать, не основываясь на таблице позиций. Важно помнить: если вы пропустите или не разберёте хотя бы один ход соперника (проигрывающего игрока), это может быть чревато тем, что данная стратегия может оказаться в корне неверной. Также нужно внимательно отнестись к расстановке знаков «`+`» и «`-`» в таблице позиций: один неверно поставленный знак может изменить ответ. Лучше не торопиться и расставить только те знаки, в которых вы уверены на данный момент. И не существенно, если вы не поставите никакого знака в данной позиции на определенном этапе (например, по правилам его необходимо поставить, но вы этого не заметили). Главное – не поставить неверного знака.




    [1] «`+`»-позиции иногда называют `"P"`-позициями, а «`-`»-позиции – `"N"`-позициями по первым буквам английских слов «Previous» (предыдущий) и «Nеxt» (следующий), указывающими, какой из игроков выиграет при старте из этой позиции – игрок, который пришёл в эту позицию последним ходом, или игрок, совершающий следующий ход из этой позиции.


    [2] Недопустимо, чтобы из этой позиции один ход вёл  в позицию, обозначенную знаком  «`+`»,  а  другой  –  вёл  в  позицию,  ещё  не  обозначенную ни одним из знаков.


    [3] Хотя убедиться в этом непросто, мы предлагаем читателю самостоятельно подумать, почему это верно.


    [4] Хотя таблица должна быть бесконечной (количество камней может быть сколь угодно большим), достаточно нарисовать таблицу `17` x `17` – случаи, когда в одной из куч более `16` камней, нас не интересуют, так как все эти позиции являются выигрышными.

  • 3.3. Дерево игры

    Данный способ является разновидностью анализа с конца и заключается в том, что мы будем анализировать в знаках «`+`» и «`-`» не все позиции, а только те, в которые можно прийти из начальной позиции. Для этого мы нарисуем дерево ходов из начальной позиции. Разберём этот метод на примере 10.

    Первоначальная позиция - `(2,3)`. За один ход из этой позиции можно прийти в позиции: `(3,3)`; `(2,4)`; `(6,3)`; `(2,9)`, добавляя один камень в одну из куч или умножая количество камней в куче на три.

    Наша цель, в конечном счёте, во все эти позиции поставить знаки «`+`» и «`-`». Чтобы поставить знак «`+`», нужно быть уверенным, что все ходы из этой позиции ведут в «`-`»; для того, чтобы поставить знак «`-`», нужно, чтобы хотя бы один ход из этой позиции вел к «`+`».

    Выше приведённое означает, что если из позиции за один ход можно прийти в позицию с количеством камней, не меньшим `16` (что по условию задачи равносильно выигрышу), это - позиция, выигрышная для первого игрока, т. е. позиция «`-`». В связи с этим знаки «`-`» можно поставить в позициях `(6,3)` и `(2,9)`, умножая количество камней в большей куче на `3`, мы получим `6^(**)3+3=21` и `2+9^(**)3=29` камней соответственно, и выиграем.

     Мы не сможем такого утверждать для позиций `(3,3)` и `(2,4)`, поэтому отразим в дереве все позиции, в которые мы можем прийти из них ещё за один ход. Две из полученных после двух ходов позиций повторяются (это позиция `(3,4)`). Можно не делать дубликат позиции `(3,4)`, а провести к ней пути как из позиции `(2,4)`, так и из позиции `(3,3)`. А можно - оставить как есть, что в данном случае мы и сделаем.

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

    Из всех полученных позиций можно за один ход дойти до выигрышных. Поэтому, в них можно поставить «`-`» и далее дерево ходов не продолжать. Теперь, посмотрим на позиции `(3,4)` и `(4,3)`. Все ходы в этих позициях ведут в позиции со знаком «`-`», т. е. в позиции, проигрышные для пришедшего в них игрока (и выигрышные для начинающего с них игрока). Поэтому, начинающий из такой позиции при правильной игре проиграет - это позиции «`+`».

    После этого, отметим знаком «`-`» позиции `(3,3)` и `(2,4)` уровнем выше как позиции, из которых существует хотя бы один ход в позицию, отмеченную знаком «`+`». И, наконец, позицию `(2,3)` отметим знаком «`+`» как позицию, все ходы из которой ведут в позиции со знаком «`-`».

    Таким образом, в позиции `(2,3)` стоит знак «`+`»[1], а это означает, что в данной игре выиграет второй игрок. Его стратегия формулируется тем же правилом, что и ранее: делать ходы в позиции, отмеченные знаком «`+`». Стратегия выигрывающего игрока в явном виде («образец оформления примера») уже была описана ранее. Аналогично анализу с конца обратим внимание, что важно построить дерево позиций до конца - пропуск любой, даже самой маленькой, ветви может существенно поменять всю расстановку знаков в вершинах дерева существенно поменять всю расстановку знаков в вершинах дерева и даже привести к тому, что победит другой игрок. Причём последнее не является редкостью.

    Отдельно отметим, что хотя «анализ с конца» и «дерево игры» являются различными вариациями одной и той же идеи, в некоторых случаях быстрее действовать одним методом, а в некоторых - другим. Так, если в игре легко отобразить схематично всё множество позиций (например, на клетчатом листе), с другой стороны, количество ходов до выигрыша может быть довольно большим (см. пример 9), гораздо легче действовать методом «анализ с конца». В примере 10 решения обоими методами примерно идентичны по трудозатратам.

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


    [1] Поскольку данное дерево игры заполнялось знаками по тем же правилам, что и таблица позиций, знаки «`+`» и «`-`» в позициях, отмеченных на дереве  и в таблице позиций ранее, должны совпадать.

  • 3.4. Детальный анализ игры

    Данный параграф появился в связи с тем, что с 2015 года в ЕГЭ в задаче по теме теории игр требуется не только указать стратегию выигравшего, но и провести более подробный анализ, нарисовав дерево игры (о чём прямо сказано в условии) и ответив на дополнительные вопросы вида «из каких позиций выиграет первый игрок, причем ровно за два хода» или «какое максимальное количество ходов потребуется для выигрыша». Условие такой задачи в реальном ЕГЭ будет, скорее всего, очень длинным и занимать до страницы; однако этого не нужно бояться.

    Пример 11

    Два игрока играют в следующую игру. Перед игроками лежит куча из `S` камней, игроки по очереди могут за ход провести над кучей следующую операцию: добавить `1` или `4` камня в кучу или, если количество камней в куче чётно, увеличить количество камней в куче в `1,5` раза. Выигрывает игрок, после чьего хода в куче будет не менее `31` камня.

    Укажите все значения `S`, при которых в правильной игре

    А) Первый игрок может выиграть первым ходом.

    Б) Второй игрок может выиграть первым ходом.

    В) Первый игрок может выиграть вторым ходом, при этом он не может выиграть своим первым ходом.

    Г) Найдите хотя бы одно значение `S`, при котором в правильной игре выигрывает второй игрок, при этом он не может выиграть своим первых ходом.


    Решение

    Нарисуем клеточную прямую и отметим знаком «`+`» выигрышные позиции в конце игры - позиции с количеством камней не менее `31`. Далее отметим знаками «`-`» позиции, из которых до указанных можно дойти за `1` ход: это позиция (`30`), из которой можно выиграть ходом «добавить `1` камень», позиции `(30)`, `(29)`, `(28)`, `(27)` из которых выиграть ходом «прибавление `4` камня» и позиции `(22)`, `(24)`, `(26)`, `(28)`, `(30)`, из которых можно выиграть за ход «увеличить кучу с чётным количеством камней в `1,5` раза»[1]. Из некоторых позиций, как видно выше, существует сразу несколько выигрышных ходов, однако это неважно: должен существовать хотя бы один. Чтобы отличать эти отмеченные позиции от всех других, добавим ещё цифру «`1`» к позиции для удобства, получив «`-1`».

    Найдя позиции, которые подпадают под условие пункта А), перейдём к пункту Б). Нас интересует не просто дальнейшая расстановка плюсов и минусов в позициях, а ещё и количество ходов до выигрыша. Фраза «второй игрок выиграет первым ходом» означает, что из данных позиций не должно быть ходов ни в какие другие позиции, кроме как в позиции, отмеченные знаком «`-1`», т. е. позиции, из которых второй игрок сможет выиграть за один ход. Такие позиции лучше перебрать следующим образом:

    Сначала отметим все пустые позиции знаком «?», из которых существует хотя бы один ход до позиций, уже отмеченных знаком «`-1`». Ходом «добавить один камень» можно за ход попасть в указанное множество из позиций `(25)`, `(23)`, `(21)`; ходом «добавить `4` камня» за ход можно попасть из позиций `(25)`, `(23)`, `(20)`, `(18)`, и ходом «увеличить в `1,5` раза» - из позиций `(16)`, `(18)`, `(20)`.

    Теперь для каждой из отмеченных «?» позиций проверим условие, что все допустимые ходы идут в нарисованное множество минусовых позиций.

    `(16)`, `(18)`, `(20)` - ход «`+1`» противоречит условию выше;

    `(21)` - ход «`+4`» противоречит условию выше;

    `(23)`, `(25)` - подходят. Таким образом `(23)`, `(25)` - являются позициями, в которых второй игрок выиграет за один ход. В этих позициях будет стоять знак «`+`» как в позициях, откуда все ходы идут в позиции со знаком «`-1`».

    Теперь, перейдём к пункту В). Перед этим сотрём все знаки «?», поскольку в позициях, отличных от `(23)` и `(25)` нам неизвестно, существует ли хотя бы один ход, ведущий в минусовую позицию.

    Первый игрок выиграет вторым ходом тогда и только тогда, когда не может выиграть за ход, но может прийти в позицию, из которой он, как второй игрок, выиграет первым ходом. Эти позиции уже найдены - это позиции `(23)` и `(25)`. Таким образом, нас интересуют все позиции, из которых можно за ход дойти до `(23)` и `(25)`. Это позиции `(19)`, `(21)`, `(22)`, `(24)`. Однако из позиций `(22)` и `(24)` в данный момент уже отмечены знаком «`-`», то есть из них можно выиграть за ход, а нас интересуют в данном пункте позиции, где за ход выиграть нельзя. Таким образом, в пункте В). Ответ - позиции `(19)` и `(21)`.

    Наконец, в пункте Г) нас интересуют позиции, в которых выиграет второй игрок, т. е. позиции «`+`». Отметим отличие пункта Г) от пункта Б). В пункте Г) нас интересуют позиции, в которых второй игрок выиграет и он не сможет выиграть первым ходом, как бы не ходил его соперник. В пункте Б) же нас интересуют позиции, в которых второй игрок сможет, наоборот, выиграть первым ходом как бы не ходил его соперник. Позиции «`+`», где в зависимости от хода первого игрока второй сможет выиграть как первым своим ходом, так и не первым, нас не интересуют ни в пункте Б), ни в пункте Г).

    Для решения пункта Г) просто продолжим заполнять согласно правилам таблицу позиций.

    Нас интересует позиция, в которой выиграет второй игрок, то есть позиция «`+`». С другой стороны, нас интересует позиция, из которой второй игрок не сможет выиграть своим первым ходом. Это означает, что первый игрок всеми своими ходами должен ходить в минусовые позиции, но ни одним своим ходом не сможет походить в позиции «`-1`», из которых существует выигрышный ход. Этим свойством будет обладать, например, позиция `(15)` - возможные ходы из неё будут вести в позиции `(16) ` и `(19)`, отмеченные знаком «`-`», а не «`-1`». Это будет наибольшей позицией, обладающей таким свойством - из позиций `(18)` и `(20)`, выигрышных для второго игрока, существует ход первого игрока «`1,5x`», приводящий к позициям `(27)` и `(30)`. Из этих позиций можно выиграть за ход.


    Ответ

    А) `(22), (24), (26), (27), (28), (29), (30)`;

    Б) `(23), (25)`;

    В) `(19), (21)`

    Г) Например, `(15)`.

    Пример 12

    Два игрока играют в следующую игру. Перед игроками лежит две кучи: в первой куче `5` камней, во второй куче - `S` камней. Игроки по очереди могут за ход провести над одной из куч следующую операцию: добавить `2` камня в кучу или, если количество камней в куче чётно, увеличить количество камней в куче в `2,5` раза. Выиграет игрок, после чьего хода суммарное количество камней в обеих кучах будет не менее `39`.

    Укажите все значения `S`, при которых в правильной игре

    А) Первый игрок может выиграть первым ходом

    Б) Второй игрок может выиграть первым ходом.

    В) Первый игрок может выиграть вторым ходом, но не может выиграть первым ходом.


    Решение

    Данный пример очень похож по условию на пример 11, однако здесь возникает проблема в том, что количество куч - две, хоть и задано, что изначально в первой куче `5` камней. Если рассматривать двумерную таблицу позиций, это приведёт к побочному анализу многих позиций, в которых количество камней в первой куче отлично от пяти (в примере 11 таких «лишних» позиций не было). Построение дерева игры также не приведёт к быстрому результату, т. к. начальная позиция неизвестна, и такие деревья нужно будет рисовать при каждом `S`.

    В связи с этим проведём предварительный анализ игры, не пользуясь ни таблицей позиций, ни деревом игры.

    А) Первый игрок выиграет за ход. Первый игрок не может увеличить количество камней в кучке из `5` камней в `1,5` раза. Следовательно, его возможные ходы – это либо добавление камней к одной из куч, либо увеличение количества камней в второй куче в `2,5` раза. В первом случае суммарное количество камней до увеличения должно равняться `37` или `38` (т. е. во второй куче `32` или `33` камня). Во втором случае: пусть `x` – количество камней во второй куче. Тогда `x` - чётно и `2,5x+5>=40`, откуда `x>=14`.  Следовательно, возможное количество камней во второй куче, при котором первый игрок победит за ход - `14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 33` (больше `33` нельзя, т. к. изначальное суммарное количество камней должно быть меньше `39`, чтобы игра имела смысл).

    Б) Второй игрок выиграет за ход. Это должны быть позиции, при которых первый игрок не сможет выиграть за ход, а второй игрок – сможет выиграть за ход после любого хода первого игрока. Рассмотрим все возможные ходы первого игрока:

    – первый игрок увеличивает количество камней в куче с `S` камнями на `2` (этот ход второй игрок может применить в любой ситуации). Тогда второй выиграет, если после этого `S` станет равно `14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 33` (см. предыдущий пункт, количество камней в первой куче не менялось), т. е. изначально `S` могло быть равно `12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 31`.

    Из этих вариантов числа камней `S` только варианты `12,31` соответствуют тому, что первый игрок не может выиграть своим первым ходом. Будем далее рассматривать только `S=12`, `S=31` и проверим оставшиеся возможные ходы первого игрока.

    Проверим `S=12:`

    – первый игрок увеличивает количество камней в куче с `S` камнями в `2,5` раза, получив `30` камней во второй куче. В таком случае второй игрок сможет выиграть за ход, также увеличив количество камней в этой куче в `2,5` раза.

    – первый игрок увеличивает количество камней в куче с `5` камнями в `2,5` раза: невозможный ход.

    – первый игрок увеличивает количество камней в первой куче на `2`. Таким образом, в первой куче - `7` камней, во второй - `12`. Однако в данном случае второй игрок не сможет выиграть - ни один из его ходов не приводит к ситуации, когда суммарное количество камней после его хода не менее `39`.

    `S=12` не подходит. Проверим `S=31:`

    – первый игрок увеличивает количество камней в куче с `S` камнями в `2,5` раза: невозможный ход.

    – первый игрок увеличивает количество камней в куче с `5` камнями в `2,5` раза: невозможный ход.

    – первый игрок увеличивает количество камней в куче с `5` камнями на `2`. Тогда второй игрок также увеличит количество камней в одной из куч на `2`, получит суммарное количество камней - `39`, и победит!

    `S=31` подходит.

    В) Первый игрок выиграет вторым ходом. После своего хода он должен прийти в позицию, из которой он (будучи «вторым» игроком), сможет выиграть за ход (т. е. в позицию, соответствующую п. Б).

    Если после хода первого игрока количество камней в первой куче останется равным `5`, то мы придём в ситуацию предыдущего пункта (после хода первого игрока, если они поменяются ролями, первому игроку, находящемуся в роли второго игрока, нужно выиграть за оставшийся ход). Ответ пункта Б) гласит, что после хода первого игрока количество камней во второй куче должно стать `31`. Единственная возможная ситуация - `29` камней.

    Заметим, что при начальном количестве в `5` камней для первой кучи и `29` камней во второй куче единственно возможные ходы - добавления по `2` камня к одной из куч. При этом никогда чётного количества камней в какой-либо из куч не получится, и применить ход «увеличить в `2,5` раза» также будет невозможно. Поэтому при любых ходах как первого, так и второго игрока, через три хода суммарное количество камней станет `29+5+2+2+2=40`, поэтому игра закончится за три хода победой первого игрока (своим вторым ходом).

    Второй случай - если первый игрок в правильной игре своим ходом поменяет количество камней в куче единственно возможным ходом «`+2` камня», при этом после хода первого игрока получится `7` камней в первой куче и `S` - во второй. После этого мы должны для начала полностью повторить анализ, по образцу предыдущего пункта. Рассмотрим следующий ход второго игрока:

    – второй игрок увеличивает количество камней в куче с `S` камнями на `2`. Тогда первый выиграет, если после этого `S` станет равно `14, 16, 18, 20, 22, 24, 26, 28, 30, 31 ` `(2,5x+7>=39)`, т. е. изначально `S` могло быть равно `12, 14, 16, 18, 20, 22, 24, 26, 28, 29`.

    Из этих вариантов числа камней `S` только варианты `12, 29` соответствуют тому, что второй игрок не может выиграть своим первым ходом. Вариант `S=29` уже был рассмотрен ранее - он подходит. Рассмотрим `S=12` и оставшиеся возможные ходы второго игрока.

    – второй игрок увеличивает количество камней в куче с `12` камнями в `2,5` раза. При этом он получит `30` камней во второй куче, и `37` - суммарно в обеих кучах. Любой ход первого игрока приведёт к выигрышу.

    – первый игрок увеличивает количество камней в первой куче в `2,5` раза: невозможный ход, т. к. `7` - нечётное число.

    – первый игрок увеличивает количество камней в первой куче на `2`. Таким образом, в первой куче - `9` камней, во второй - `12`. В данном случае первый игрок сможет выиграть за ход, увеличив количество камней во второй куче, `12`, в `2,5` раза: `9+30 = 39` камней, ровно столько, сколько и требуется для победы. Любой другой ход первого игрока приведёт к тому, что после этого его соперник увеличит в `2,5` раза кучу из `12` или `14` камней и победит, т. о., этот ход не является ходом первого игрока при правильной игре (см. замечание после данной задачи).

    Итак, `S=12` будет подходить под условие «первый игрок всегда выиграет вторым ходом», если первым ходом первый игрок увеличит количество камней в первой куче с `5` до `7`. Заметим, что все остальные ходы первого игрока `(12->14; 12->30)` приведут к тому, что второй игрок увеличит количество камней во второй куче в `2,5` раза и выиграет с суммарным количеством камней `40` и `80`. Следовательно, такие ходы первого игрока не могут быть ходами в правильной игре.


    Ответ

    А) `S=14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 33`;

    Б) `S=31`;

    В) `S=12,29`.





    [1] Если в условии явно просят объяснить, откуда возникают вышеуказанные позиции и почему других таких позиций нет (это встречено автором в демоверсии ЕГЭ 2015), желательно выписать возникающие неравенства и их решить. Иначе в случае сильно строгой проверки можно недосчитаться первичных баллов на пустом месте. Так, в случае хода «умножить кучу с чётных количеством камней на 1,5» нужна система условий, состоящая из 1) неравенства  (невыигрышная априори); 2) неравенства  (можно дойти за 1 ход до выигрышной) и 3)  чётно.

     




     



  • §12. Примеры задач на операторы цикла
    задача 1

    Ввести значения $$ x$$ и $$ n$$. Вычислить сумму ряда $$ 1+x+{x}^{2}+{x}^{3}+\dots +{x}^{n}$$.

    Решение

    Сумма вычисляется накопительным путём: сначала вычисляется каждое слагаемое, а потом оно добавляется в сумму. Каждое слагаемое также вычисляется накопительным путём.
        var x,n,i,s,a:integer;
          begin
            write('введите значения x и n');
            readln(x,n);
            S:=1; a:=1
            for i:=1 to n do
              begin
                a:=a*x; s:=s+a
              end;
            writeln(s);
            readln
          end.
    Заметим, что эту задачу можно решить, воспользовавшись формулой суммы первых `n` членов геометрической прогрессии, но наша цель не решить конкретную задачу, а понять принцип накапливания (суммы). Ибо далеко не любая последовательность чисел является геометрической или арифметической прогрессией.

    задача 2

    Вводится последовательность натуральных чисел. Признак конца ввода – число `0`. Вывести среднее арифметическое чисел из последовательности (`0` не учитывается). 

    Решение

    Эта задача относится к очень важному классу задач – обработка последовательностей с признаком конца и заранее неизвестным количеством элементов. В таких задачах нужно пользоваться операторами while и repeat. Причём, если оговаривается, что последовательность непустая, можно использовать repeat, а если этого не оговаривается, как в данной задаче, то нужно обязательно пользоваться циклом while, так как цикл repeat всегда выполняется, по крайней мере, один раз. Приведём полный текст решения. Переменная `s` подсчитывает сумму элементов последовательности, а переменная `k` – их количество.

        var a,s,k:integer;
          begin
            s:=0; k:=0;
            writeln('Введите последовательность натуральных чисел. Ноль – признак конца');
            read(a);
            while a<>0 do
              begin s:=s+a; k:=k+1; read(a) end;

            Writeln('среднее арифметическое=',s/k:0:4)
          end.
    Обратите внимание, что для ввода данных используется оператор read. Это позволяет набивать элементы в строчку через пробел. Если же использовать readln, то набивать значения придётся в столбик, что неудобно. Ещё одно важное замечание в этой задаче – оператор ввода внутри цикла должен быть последним, чтобы сразу попадать на проверку признака конца. Эти замечания относятся ко ВСЕМ задачам на обработку последовательностей с признаком конца.

    задача 3

    Ввести целое число `n`. Вывести YES, если оно простое, и NO, если оно составное.

    Решение

    Эта задача демонстрирует сразу две важные вещи. Во-первых, как проверять делимость целых чисел, а во-вторых, технику флажков. Флажком называется переменная, которая имеет некоторое начальное значение и меняет его, если происходит определённое событие. Как правило, флажок имеет тип boolean.
    В нашей задаче мы будем перебирать числа от `2` до квадратного корня из `n` и проверять, делится ли `n` на каждое из них. Изначально предположим, что `n` – простое, и присвоим флажку значение true, но если `n` поделится на какое-нибудь число, это будет значить, что оно составное, и, соответственно, флажок «упадёт» на значение false. Проверять на делимость нужно, сравнивая остаток от деления с нулём.
        var n,i:integer;
        f:boolean;
          begin
            Write('Введите значение n ');
            Readln(n);
            f:=true;
            for i:=2 to round(sqrt(n)) do
              if n mod i = 0 then f:=false else;
              if f=true
                then writeln('YES')
                else writeln('NO');
            Readln
          end. 

  • §11. Операторы цикла

    Зачастую в задаче нужно повторять одни и те же действия много раз. Повтор некоторого фрагмента программы несколько раз называется циклом. Рассмотрим следующий пример.

    Пример 9

    Вывести на экран квадраты чисел от `1` до `100`.

    Решение

    Очевидно, что для решения этой задачи нам придётся `100` раз выполнять команду вывода соответствующего числа на экран. Писать `100` операторов вывода как-то не хочется (слишком трудоёмко), поэтому будем знакомиться с операторами цикла. В языке Pascal существует три оператора цикла: for, while, repeat. Начнём с цикла for. Этот оператор цикла реализует следующую идею: «Повторять некоторую последовательность команд `N` раз, где `N` известно до начала повторения». Познакомимся с синтаксисом этого оператора.

    for имя переменной := начальное значение to конечное значение do оператор

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

     Алгоритм выполнения цикла for следующий:
    1) вычисляются начальное и конечное значения;
    2) счётчику цикла присваивается начальное значение;
    3) значение счётчика сравнивается с конечным. Если оно больше конечного, то выполнение цикла завершается и начинает выполняться следующий оператор программы, в противном случае переход к пункту 4;
    4) выполняется тело цикла;
    5) значение счётчика увеличивается на `1`;
    6) переход к пункту 3.

    В качестве примера рассмотрим решение задачи, поставленной выше. В качестве счётчика будем использовать переменную `i`.
        var i:integer;
          begin
            for i:=1 to 100 do write(i*i,' ');
            Readln
          end.
    Согласитесь, что решение фактически в одну строчку выглядит гораздо приятнее, чем в `100` строк (если не пользоваться оператором цикла).

    Необходимо сделать несколько замечаний по поводу цикла for

    1) Типы счётчика начального и конечного значений должны совпадать, при этом в настоящий момент из известных нам типов можно использовать только integer и boolean. Вещественный тип использовать нельзя.
    2) Начальное и конечное значения вычисляются один раз до начала цикла (и после не перевычисляются). Рассмотрим пример.

    i:=1; for i:=i to i do writeln('HI');

    Этот оператор цикла выполнится всего один раз, а не бесконечно много.
    3) В теле цикла значение счётчика изменять нельзя. Так прописано в стандарте языка Pascal, и это требование поддерживается в системах семейства Delphi. Однако в системах семейства Borland Pascal значение счётчика изменять можно, что может приводить к непредсказуемым последствиям (поэтому будем считать, что независимо от системы значение счётчика изменять нельзя).
    4) После завершения цикла значение счётчика не определено, то есть нельзя считать, что оно равно конечному значению или больше на единицу и пользоваться этим в дальнейшем алгоритме.
    5) Тело цикла по грамматике должно состоять только из `1` оператора. Если же там по алгоритму должно быть несколько, нужно использовать составной оператор.
    6) Можно слово to заменить на слово downto. В этом случае значение счётчика после каждого выполнения тела цикла будет уменьшаться на `1`, а выход из цикла произойдёт, когда значение счётчика окажется меньше, чем конечное.

    Перейдём к знакомству с другими операторами цикла: while и repeat. Эти операторы реализуют следующие идеи: «Повторять некоторую последовательность команд пока выполняется некоторое условие» (цикл while) и «Повторять некоторую последовательность команд до тех пор, пока не выполнится некоторое условие» (цикл repeat). Познакомимся с синтаксисом оператора цикла while

    while условие do оператор.

    После слова while должно быть логическое выражение (называемое условием), которое может принимать значение true или false. Оператор, стоящий после do, аналогично оператору for является телом цикла. Выполняется цикл while так:
    1) вычисляется значение условия, если получилось true, то переход к пункту 2, иначе выход из цикла и переход на следующий оператор программы;
    2) выполняется тело цикла;
    3) переход к пункту 1.
    Фактически оператор while является многократным применением оператора if с пустой веткой else. Аналогично оператору for, тело цикла должно состоять из `1` оператора. Однако в отличие от цикла for возможна ситуация, когда цикл будет выполняться бесконечное количество раз (зациклится). Например, while 2*2=4 do...

    Что написать после do, совершенно не важно, важно, что оно будет выполняться, пока 2*2=4, а это всегда так и никогда не изменится. 

    Значит, чтобы избегать зацикливания, параметры условия должны быть переменными, например while x*x=4 do ... Хотя это тоже не гарантирует отсутствие зацикливания.

    Осталось познакомиться с последним оператором цикла. Рассмотрим его синтаксис.
        repeat
        Оператор 1;
        Оператор 2;
        ....
        Оператор N
        until условие

    Все операторы, написанные между repeat и until, являются телом цикла. Это выгодно отличает оператор repeat от других циклов – составной оператор здесь не требуется, а операторными скобками можно считать слова repeat и until. Работает этот оператор по следующему алгоритму: 
    1) выполняется тело цикла;
    2) вычисляется значение условия. Если получилось true, то выход из цикла и переход к следующему оператору программы, в противном случае переход к пункту 1.
    Отличительная особенность оператора цикла repeat заключается в том, что тело всегда выполняется, по крайней мере, один раз. Это нужно учитывать в задачах при выборе оператора цикла. Аналогично оператору while, цикл repeat может зациклиться, правда в случае, когда условие никогда не принимает значение true, например, 

    repeat...until 2*2=5.

        

  • §10. Условный оператор

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

    Пример 7

    Ввести номер года. Вывести слово YES, если год високосный, и NO, если он – не високосный.

    Решение

    По условию очевидно, что в зависимости от входных данных программа должна будет выполнить один из двух операторов вывода: Writeln('YES') или Writeln('NO'). При этом написать в программе нам придётся оба, а вот выполняться должен будет только один из них. Для того чтобы реализовывать подобные ветвления алгоритма, в языке Pascal существует условный оператор. В общем виде он выглядит следующим образом:

       if логическое выражение
         then оператор
         else оператор

    В этой конструкции слова if, then и else являются служебными зарезервированными словами языка. Работает эта конструкция так: сначала вычисляется логическое выражение, стоящее после if. Если получилось значение true, то выполняется оператор, стоящий после слова then, а если получилось значение false, то выполняется оператор, стоящий после слова else.
    Обратите внимание, что внутри условного оператора нет никаких точек с запятой, поскольку он является единой конструкцией, а точка с запятой – это разделитель между операторами. Для удобства чтения программ принято условие записывать на одной строке, а ветви then и else начинать с новой строки, однако это не является синтаксическим правилом языка.
    В качестве примера условного оператора рассмотрим решение задачи, поставленной выше. Год считается високосным, если он делится нацело на `400`, или если он делится нацело на `4`, но не делится нацело на `100`. Приведём полный текст решения: 

        var y:integer;
          begin
            write('Введите номер года ');
            readln(y);
            if (y mod 400 = 0)or(y mod 4 = 0)and(y mod 100 <> 0)
              then writeln('YES')
              else writeln('NO');
          end.

    По грамматике языка после слов then и else должен стоять только один оператор языка. То есть запись if x>0 then x:=4; y:=0 else z:=9; является синтаксически неверной. А как быть, если всё-таки нужно выполнить более одного оператора? Для таких случаев в языке Pascal предусмотрен составной оператор, который позволяет превратить группу операторов в один. Выглядит он следующим образом: сначала записывается ключевое слово begin, далее – интересующая нас последовательность операторов через точку с запятой, а в конце пишется ключевое слово end. В отличие от конца программы, точка после этого слова не ставится. Слова begin и end называются операторными скобками. Запишем правильную версию условного оператора, приведённого выше: if x>0 then begin x:=4; y:=0 end else z:=9;

    Обратите внимание на следующий тонкий момент: если требуется выполнить более одного оператора в ветке then, и при этом мы забудем написать операторные скобки, то это является синтаксической ошибкой, и программа просто не будет работать. Если же забыть написать операторные скобки в ветке else, то программа работать будет, но не так, как предполагалось. Рассмотрим пример: if x>0 then y:=9 else z:=8; c:=5; 

    В этом примере условный оператор заканчивается после z:=8; в то время как оператор c:=5; является следующим оператором программы и выполняется независимо от результата сравнения `x` с нулём. Если же написать операторные скобки, то присваивание в `c` числа `5` произойдёт только в случае x<=0

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

    Рассмотрим следующий пример.

    Пример 8

    Вводятся `3` целых числа – `a`,`b`,`c`. Требуется в переменную `a` записать минимальное из этих чисел, в `b` – среднее и в `c` – максимальное.

    Решение

    Алгоритм решения этой задачи такой: сначала сравним значения переменных `a` и `b`, если значение `a` – больше, поменяем их местами. После этого сравним значения переменных `a` и `c`, и если значение `a` – больше, поменяем их местами. После этих двух сравнений в переменной `a` гарантированно окажется наименьшее из трёх чисел. Осталось сравнить переменные `b` и `c`, и в случае, когда в переменной `b` находится большее значение, поменять их местами.
    Очевидно, что в этом алгоритме у нас три сравнения, следовательно, три последовательных условных оператора. При этом в каждом из них какие-то действия (поменять местами значения двух переменных) нужно выполнять только в ветке then, в ветке else (например, если в первом сравнении в переменной a находится уже более маленькое число, чем в переменной `b`) никаких действий выполнять не нужно. Но мы всё равно будем её записывать, чтобы избегать путаницы. Приведём полный текст решения, используя для обмена значений двух переменных дополнительную переменную `x`.

    var a,b,c,x:integer;
        begin
          writeln('введите три целых числа ');
          readln(a,b,c);
          if a>b then begin x:=a; a:=b; b:=x end
            else;

          if a>c then begin x:=a; a:=c; c:=x end
            else;
          if b>c then begin x:=b; b:=c; c:=x end
            else;
          writeln(a,b,c);
        end.


      

        

  • §9. Логический тип переменных

    В языке Pascal кроме уже изученных нами числовых типов ещё есть логический, который называется Boolean. Переменные этого типа занимают `1` байт оперативной памяти и могут принимать всего два значения – true и false (истина и ложь). Логическим переменным можно присваивать значения точно так же, как и числовым. Так же можно выводить их значения на экран, а вот вводить их с клавиатуры нельзя!


    В языке Pascal определены `6` операций сравнения, результатом которых является логическое значение. Это операции: «больше» (>), «больше или равно» (>=), «меньше» (<), «меньше или равно» (<=), «равно» (=), и «не равно» (<>). Например, операция 5 > 2 выдаст значение true, а операция x<>3 выдаст значение true, если переменная `X` имеет любое значение, кроме `3`. Сравнивать можно не только числа (причём как целые, так и вещественные), но и логические значения. При этом считается, что значение true больше, чем значение false.
    Помимо операций сравнения ещё существуют и логические операции: AND (конъюнкция, логическое умножение, операция «И»), OR (дизъюнкция, логическое сложение, операция «ИЛИ»), NOT (отрицание, инверсия), XOR (строгая дизъюнкция, исключающее «ИЛИ», сложение по модулю `2`). В скобках указаны возможные названия данных операций в алгебре логики. Операнды этих операций должны быть логического типа. Результат вычислений также будет логический. При этом операции AND, OR, XOR имеют по два операнда, а операция NOT – всего один, который записывается справа от названия операции. Названия логических операций являются ключевыми словами языка. Приведём таблицы результатов логических операций для всех возможных значений операндов (в алгебре логики такие таблицы называются таблицами истинности):


    x not x
    false

    true

    true

    false


    x

    y x and y

    x or y

    x xor y
    false false false false false
    false true false true true
    true false false true true
    true true true true false


    Логический результат даёт также стандартная функция odd(x), которая применяется к целочисленному аргументу `x`:
        odd(x) = true, если `x` нечётно;
        odd(x) = false, если `x` чётно.
    Приоритет операций в логическом выражении следующий:
    1) Операция NOT.
    2) Операции группы умножения AND, *, / ,div, mod
    3) Операции группы сложения OR, XOR, +, -
    4) Операции сравнения >, <, >=, <=, =, <>
    Операции одного приоритета выполняются слева направо. Операции в круглых скобках имеют более высокий приоритет, чем операции вне скобок.

    Пример 6

    Записать логическое выражение, истинное в случае, когда переменная `X` имеет значение из отрезков `[2,5]` или `[-1,1]`.

    Решение

    (X>=2) AND (X<=5) OR (abs(X)<=1).


         

  • §8. Примеры простейших программ

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

    Пример 3

    Ввести координаты трёх вершин треугольника. Вывести его площадь. Гарантируется, что треугольник существует (ввод корректный).

    Решение

    Для решения этой задачи нам потребуется `6` переменных, в которые будут введены значения координат `(x1, y1, x2, y2, x3, y3)`. Можно сделать их целыми для простоты, а можно вещественными, чтобы решать задачу в более общем случае. Так же потребуются переменные, в которые мы запишем длины сторон треугольника `(a, b, c)` и площадь `(S)`. Эти переменные однозначно должны быть вещественными, так как при вычислении расстояния между точками нам придётся извлекать квадратный корень, и, соответственно, результат получится вещественным. И ещё предлагается ввести вещественную переменную `p`, в которой будет храниться полупериметр треугольника, так как площадь будет вычисляться по формуле Герона. Приведём полный текст программы:

       Var x1,y1,x2,y2,x3,y3:integer; a,b,c,p,S:real;
          Begin
             Write('Введите координаты вершин треугольника ');
             Readln(x1,y1,x2,y2,x3,y3);
             a:=sqrt(sqr(x1-x2)+sqr(y1-y2));
             b:=sqrt(sqr(x1-x3)+sqr(y1-y3));
             c:=sqrt(sqr(x2-x3)+sqr(y2-y3));
             p:=(a+b+c)/2;
             S:=sqrt(p*(p-a)*(p-b)*(p-c));
             Writeln('Площадь треугольника равна ',S:5:2);
             Readln;
          End.

    Пример 4

    Идёт `k`-ая секунда суток (`k` вводится). Вывести, сколько полных часов `h` и полных минут `m` прошло с начала суток.

    Решение

    В этой задаче все параметры целые. Решается она с помощью операций div и mod. Эти операции можно использовать для «срезания периодов» при переводе мелких единиц измерения в более крупные (например, секунд в минуты). Операция div нам выдаст количество полных периодов (сколько полных минут содержится в большом количестве секунд), а операция mod – количество единиц в последнем неполном периоде (сколько секунд не укладывается в полное количество минут). Приведём полный текст решения.

       Var k,h,m:integer;
          Begin
             Write('Введите номер секунды в сутках');
             Readln(k);
             h:=k div 3600;
             m:=k mod 3600 div 60;
             writeln('С начала суток прошло ',h,' часов и ',m,' минут');
             readln;
          end.

    Пример 5

    Вводится четырёхзначное число. Вывести произведение его цифр.

    Решение

    Эта задача показывает ещё одно применение операций div и mod – выделение цифр из целого числа. Описанное ниже решение работает только для случая, когда количество цифр в числе заранее известно. В противном случае придётся использовать циклический алгоритм. Приведём текст решения.

       Var N,c1,c2,c3,c4:integer;
          Begin
             Write('Введите целое четырёхзначное число');
             Readln(N);
             c1:=N div 1000;
             c2:=N div 100 mod 10;
             c3:=N div 10 mod 10;
             c4:=N mod 10;
             Writeln('Произведение цифр вашего числа равно',c1*c2*c3*c4);
             Readln;
          End.

  • §7. Операторы ввода

    Операторы read и readln предназначены для задания значений переменным путём ввода их с клавиатуры. Правило их применения одно и то же: после слова read или readln в скобках через запятую перечисляются имена переменных, значения которых мы хотим ввести (список ввода). Число этих имён не ограничено. Запятая служит разделителем между именами переменных:

    readln(имя, имя, ..., имя)

    При срабатывании оператора read или readln выполнение программы будет приостановлено до тех пор, пока пользователь не введёт соответствующее количество значений. Вводимые значения должны быть того же типа, что и переменные. Если в read или readln переменных несколько, то они могут быть набиты в одной строке, но одно число от другого должно отделяться пробелом или переводом строки.
    Чтобы выполнить оператор read или readln после набивания значений с клавиатуры, нужно нажать клавишу «Enter». В результате переменные приобретут заданные вами значения. Между read и readln существует единственное различие: после выполнения readln курсор переходит на новую строку, игнорируя всю оставшуюся информацию в прежней строке, а после выполнения read курсор остаётся в той же строке, и новая набивка данных для read или readln будет проходить в той же строке. Но, так как после нажатия клавиши «Enter» курсор в любом случае переходит на новую строчку, для однократного ввода значений переменных разницу между операторами read и readln заметить невозможно. Тем не менее, в данном случае лучше использовать readln. Оператор readln можно использовать и без параметров вообще. Тогда программа просто будет находиться в режиме ожидания, пока пользователь не нажмёт клавишу «Enter». Такой оператор, например, удобно ставить самым последним оператором в программе. Тогда можно сразу посмотреть результат работы программы, а потом нажать «Enter», и только тогда работа программы завершится.

    Замечание

    Перед вводом данных с клавиатуры рекомендуется выдавать на экран приглашение, например:

        write('Введите число a => ');
        readln(a);


  • §6. Операторы вывода. Модификаторы формата

    Операторы вывода являются важнейшей частью языка программмирования, ведь только благодаря им, мы можем увидеть на экране компьютера результат работы нашей программы. В языке Pascal существует два оператора вывода: write и writeln. Правило их использования одно и то же: после слова write или writeln в скобках через запятую перечисляются параметры, которые мы хотим вывести (называемые списком вывода). Число этих параметров не ограничено. Разделителем между параметрами служит запятая: 

    writeln(параметр, параметр,...,параметр)

    Существует три вида параметров: константы, переменные и выражения (например, арифметические выражения). Константы бывают числовые (это просто различные числа — целые и вещественные), логические и строковые. Любой текст, набранный с клавиатуры и заключённый в апострофы (одиночные кавычки), называется строковой константой. Если в текст нам нужно поместить апостроф, например, в слове O'key, на этом месте нужно набить два апострофа подряд вместо одного: write('O''key'). Все параметры в write или writeln независимы друг от друга, поэтому в одном и том же операторе могут встречаться параметры разных типов, в произвольном порядке.

    При выполнении оператора вывода все параметры будут выведены в одной строке в том же порядке, в каком они перечислены в списке параметров. Любая константа, числовая или строковая, будет выведена так, как вы её написали в вызове write или writeln (в строковой константе начальный и конечный апострофы отображаться на экране не будут, а вместо двух апострофов, расположенных в строковой константе подряд, на экране появится в этом месте один); вместо переменной на экране появится её значение, а вместо арифметического выражения — результат его вычисления.
    Между write и writeln существует единственное различие: после выполнения writeln курсор переходит на новую строку, а после выполнения write курсор остаётся в той же строке, и новый вывод данных с помощью write или writeln или ввод данных при помощи операторов ввода данных будут проходить в той же строке.
    При выводе параметров пробелы между ними автоматически не вставляются, например, при печати чисел 1, 2, 3 с помощью writeln(1,2,3) все они сольются в одно число — 123. Чтобы разделить выводимые элементы, можно поместить между ними символ пробела, например, writeln(1,' ',2,' ',3) или отформатировать вывод, поставив после каждого элемента списка вывода двоеточие и целое число (называемое модификатором ширины поля), которое указывает, сколько позиций на экране должна занимать выводимая величина, например, writeln(1:3,2:3,3:3). Отметим, что элемент дополняется начальными пробелами слева с тем, чтобы соответствовать указанной после двоеточия величине. Результаты выполнения двух последних операторов будут выглядеть так:

    1_2_3
    __1__2__3

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

    При выдаче на экран значений вещественных выражений в формате вывода полезно использовать ещё один модификатор, который записывается через двоеточие после модификатора ширины поля и называется модификатором точности. Он будет обозначать количество символов после десятичной точки, которые мы хотим вывести. Например, при выводе результата стандартной функции pi, которая с машинной точностью выдаёт значение числа $$ \pi $$, оператор write(pi:0:0,pi:6:2, pi/2:2:0) выдаст на экран:

    3 3.14 2

    Заметим, что при печати фиксированного количества цифр вещественного числа оно предварительно округляется по правилам математики. Если вещественное число содержит после десятичной точки меньше цифр, чем количество символов для печати, указанное в модификаторе точности, то число выводится с незначащими нулями, например, оператор write(3.14:3:4) выдаст на экран:

    3.1400

    Модификатор точности можно применять только к параметрам вещественного типа. Использование модификатора точности с параметрами других типов является критической ошибкой (программа не будет работать). Модификатор ширины поля можно использовать с любым типом параметра вывода.

  • §5. Арифметические выражения

    Арифметические выражения состоят из операций и операндов. В языке программирования Pascal существует шесть операций: сложение (обозначается знаком «+»), вычитание (обозначается знаком «-»), умножение (обозначается знаком «*»), деление (обозначается знаком «/»), деление нацело (обозначается словом «div») и взятие остатка от деления нацело (обозначается словом «mod»). Слова div и mod являются служебными зарезервированными.

    Важным понятием в арифметике является понятие операнда. Операндами называются те объекты, над которыми выполняется арифметическая операция. В математике различные операции могут иметь разное количество операндов, но все арифметические имеют два операнда. Операндом для операции может являться как одиночное число или имя переменной, так и целое арифметическое выражение. Рассмотрим выражение (2+2)*2. У операции сложения операндами являются два числа `2`, а у операции умножения правый операнд – это число `2`, а левый – это выражение в скобках `(2+2)`. Прежде чем выполнять операцию, необходимо вычислить оба её операнда. 
    Приоритет операций в Паскале точно такой же, как и в математике. Сначала выполняются операции умножения, деления, div и mod (это тоже операции деления), а потом операции сложения и вычитания. Операции одного приоритета выполняются слева направо. Для изменения порядка действий можно использовать круглые скобки. Операции в скобках имеют более высокий приоритет, чем операции вне скобок. Так при вычислении выражения 2+2*2 получается число `6`, потому что операция умножения имеет более высокий приоритет, чем сложение, и, следовательно, выполняется первой. Если же записать выражение (2+2)*2, то при вычислении получается число `8`, потому что сложение в скобках выполняется раньше умножения.
    Рассмотрим, как определить тип результата при вычислении арифметического выражения. Операции сложения, вычитания и умножения выдают целый результат, если оба их операнда целые, и вещественный, если хотя бы один из операндов – вещественный. Операция деления «/» всегда выдаёт вещественный результат. Даже если мы `4` делили на `2`, всё равно в итоге получается нецелое число. На первый взгляд это кажется странным, но в отличие от математики в программировании каждое число кроме значения ещё имеет тип, и если типы у чисел не совпадают, то они НЕ считаются равными. Нужно уяснить, что `bb(1!=1.0)`. Это несложно понять, если помнить, что раз числа `1` и `1.0` имеют различные типы, то будут представлены совершенно разными последовательностями битов. Операции div и mod всегда выдают целый результат и, в отличие от всех остальных арифметических операций, могут иметь только целые операнды. Попытка применить данные операции к вещественным числам приведёт к тому, что программа просто не будет работать.

    Давайте подробнее познакомимся с двумя последними операциями. Операция a div b выдаёт целую часть от деления числа a на число b. То есть 5 div 2 = 2, а 3 div 7 = 0. Операция a mod b выдаёт остаток от деления a на b по следующему закону:

    a mod b = a – ((a div b) * b)

    Приведём примеры выполнения этих их операций для всех возможных знаков операндов:

    Примеры

     5 div 3 = 1; 5 mod 3 = 2;
    -5 div 3 = -1; -5 mod 3 = -2;
     5 div -3 = -1; 5 mod -3 = 2;
    -5 div -3 = 1; -5 mod -3 = -2;
        

    Операндами в арифметическом выражении также могут быть стандартные математические функции, которые приведены в таблице ниже.

    Функция Комментарий Тип аргумента Тип результата
    abs(x)  $$ │x│$$ — модуль $$ x$$ integer, real совпадает с типом аргумента
    sqr(x) $$ {x}^{2}$$ integer, real совпадает с типом aргумента
    sqrt(x) `sqrt x`$$ $$ — корень  квадратный из $$ x$$ integer, real real
    Pi `3.1415926535897932385` нет real
    sin(x) $$ \mathrm{sin}x$$ integer, real real
    cos(x) $$ \mathrm{cos}x$$ integer, real real
    arctan(x) `"arctg"x` integer, real real
    trunc(x) отсекание дробной части $$ x$$ real integer
    round(x) округление $$ x$$ до ближайшего целого. Половины округляются в сторону увеличения модуля.  real integer

    Необходимо отметить, что функциям sin и cos угол следует подавать в радианах, а не в градусах! Также функция arctan возвращает результат в радианах.

  • §4. Числовые типы переменных. Оператор присваивания

    Рассмотрим два основных числовых типа переменных.

    1) INTEGER

    Этот тип характеризует целые числа. Переменные этого типа занимают в оперативной памяти `4` байта и могут принимать значения из диапазона

    `[ –2147483648, 2147483647]`.

    Точное значение запоминать необязательно, главное помнить, что переменная этого типа может вмещать целые числа примерно до `2` миллиардов по модулю. 

    2) REAL

    Этот тип предназначен для работы с вещественными (действительными) числами. Переменные этого типа занимают в оперативной памяти `8` байт. При записи констант этого типа целая часть числа отделяется от дробной точкой, а не запятой, как в математике, например: `3.14`.

    Оператор присваивания позволяет изменить значение любой переменной программы (присвоить ей новое значение). Этот оператор выглядит следующим образом: записывается имя переменной, затем знак присваивания (:=), а потом значение, которое мы хотим присвоить переменной. Присвоить можно константу или выражение соответствующего типа.

    Пример 1

    X:=5; {в переменную `X` присвоили число `5`}
    Y:=X;
    {в переменную `Y` присвоили текущее значение переменной `X`}
    Z:=X+Y;
    {в переменную `Z` присвоили сумму текущих значений переменных `X` и `Y`}   

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

    Пример 2

    X:=5;
    Y:=X+4; {в переменную `Y` запишется число `9`, так как текущее значение переменной `X` равно `5`}  

    При использовании операторов присваивания необходимо соблюдать правило совместимости типов.

    правило совместимости типов

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

    Есть исключение из этого правила:

    переменной типа real можно присвоить целое значение.

  • §3. Константы и переменные

    Познакомимся с двумя важнейшими в программировании понятиями.

    Определение 1 

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

    Определение 2

    Переменной назывется объект, который может менять своё значение в ходе выполнения программы.

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

    Правила именования в языке Pascal следующие: именем может являться любая последовательность латинских букв и цифр, начинающаяся с буквы. При этом заглавные и строчные латинские буквы не различаются, то есть имена aBBA и AbBa на самом деле обозначают один и тот же объект. Ещё одна интересная особенность построения имён заключается в том, что символ подчёркивания ( _ ) считается латинской буквой, поэтому он также может входить в состав имени и, более того, имя с него может начинаться. Последнее правило именования заключается в том, что имена переменных или констант не должны совпадать со служебными зарезервированными словами языка.
    Каждая переменная и каждая константа помимо имени ещё имеет свой тип. Тип определяет три вещи: 

    1) размер области оперативной памяти, отводимой под соответствующую переменную;
    2) множество значений, которые может принимать соответствующая константа или переменная;
    3) набор операций, которые можно выполнять с соответствующей константой или переменной.

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

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

    Пример 

    var x:integer;

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

    Пример 

    var x,y:integer;

    Для описания константы необходимо записать ключевое слово  const, затем указать имя константы (отделив его как минимум одним пробелом от ключевого слова), поставить знак равенства и тут же задать её значение, например:

    Пример 

    const N=1000;

    После окончания описания константы также ставится точка с запятой. 

    В программе принято сначала описывать константы (если они есть), а уже затем переменные (а они есть практически в любой программе).

    Замечание

    Термином «константа» в программировании принято обозначать ещё одно понятие – если в программе встречается некоторое конкретное значение (например, число `1000`), то оно также называется константой соответствующего типа.