16 статей
В языке 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) Операции сравнения >, <, >=, <=, =, <>
Операции одного приоритета выполняются слева направо. Операции в круглых скобках имеют более высокий приоритет, чем операции вне скобок.
Записать логическое выражение, истинное в случае, когда переменная `X` имеет значение из отрезков `[2,5]` или `[-1,1]`.
(X>=2) AND (X<=5) OR (abs(X)<=1).
Теперь воспользуемся всеми полученными знаниями и рассмотрим примеры простейших программ. Условимся в примерах не различать заглавные и строчные буквы, а ключевые слова выделять жирным шрифтом.
Ввести координаты трёх вершин треугольника. Вывести его площадь. Гарантируется, что треугольник существует (ввод корректный).
Для решения этой задачи нам потребуется `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.
Идёт `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.
Вводится четырёхзначное число. Вывести произведение его цифр.
Эта задача показывает ещё одно применение операций 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.
Операторы 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);
Операторы вывода являются важнейшей частью языка программмирования, ведь только благодаря им, мы можем увидеть на экране компьютера результат работы нашей программы. В языке 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
Модификатор точности можно применять только к параметрам вещественного типа. Использование модификатора точности с параметрами других типов является критической ошибкой (программа не будет работать). Модификатор ширины поля можно использовать с любым типом параметра вывода.
Арифметические выражения состоят из операций и операндов. В языке программирования 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` |
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` байта и могут принимать значения из диапазона
`[ –2147483648, 2147483647]`.
Точное значение запоминать необязательно, главное помнить, что переменная этого типа может вмещать целые числа примерно до `2` миллиардов по модулю.
Этот тип предназначен для работы с вещественными (действительными) числами. Переменные этого типа занимают в оперативной памяти `8` байт. При записи констант этого типа целая часть числа отделяется от дробной точкой, а не запятой, как в математике, например: `3.14`.
Оператор присваивания позволяет изменить значение любой переменной программы (присвоить ей новое значение). Этот оператор выглядит следующим образом: записывается имя переменной, затем знак присваивания (:=), а потом значение, которое мы хотим присвоить переменной. Присвоить можно константу или выражение соответствующего типа.
X:=5; {в переменную `X` присвоили число `5`}
Y:=X; {в переменную `Y` присвоили текущее значение переменной `X`}
Z:=X+Y; {в переменную `Z` присвоили сумму текущих значений переменных `X` и `Y`}
Если переменной присвоено некоторое значение, то в дальнейшем в программе при вычислениях вместо её имени будет подставляться это значение, пока мы не присвоим ей новое.
X:=5;
Y:=X+4; {в переменную `Y` запишется число `9`, так как текущее значение переменной `X` равно `5`}
При использовании операторов присваивания необходимо соблюдать правило совместимости типов.
Это правило заключается в том, что тип присваемого значения должен соответствовать типу переменной, которой мы хотим это значение присвоить.
Есть исключение из этого правила:
переменной типа real можно присвоить целое значение.
Познакомимся с двумя важнейшими в программировании понятиями.
Константой назывется объект, который получает значение до начала выполнения программы и не может менять его в ходе выполнения.
Переменной назывется объект, который может менять своё значение в ходе выполнения программы.
С каждой переменной, используемой в программе, связывается область в оперативной памяти компьютера, размер которой зависит от типа объекта. Каждая константа и каждая переменная имеет своё имя (идентификатор), по которому мы и обращаемся к этим объектам, чтобы с ними работать. Каждое имя должно быть уникальным, чтобы не возникала ситуация неопределённости – какой объект выбирать. Если в программе две переменные имеют одно и то же имя, компьютер откажется выполнять данную программу.
Правила именования в языке Pascal следующие: именем может являться любая последовательность латинских букв и цифр, начинающаяся с буквы. При этом заглавные и строчные латинские буквы не различаются, то есть имена aBBA и AbBa на самом деле обозначают один и тот же объект. Ещё одна интересная особенность построения имён заключается в том, что символ подчёркивания ( _ ) считается латинской буквой, поэтому он также может входить в состав имени и, более того, имя с него может начинаться. Последнее правило именования заключается в том, что имена переменных или констант не должны совпадать со служебными зарезервированными словами языка.
Каждая переменная и каждая константа помимо имени ещё имеет свой тип. Тип определяет три вещи:
1) размер области оперативной памяти, отводимой под соответствующую переменную;
2) множество значений, которые может принимать соответствующая константа или переменная;
3) набор операций, которые можно выполнять с соответствующей константой или переменной.
Для того чтобы определить в программе константу или переменную, её нужно сначала описать до раздела действий.
Переменные описываются следующим образом: сначала записывается ключевое слово var, затем ставится пробел, указывается имя, которое мы хотим дать нашей переменной, ставится двоеточие и записывается тип переменной. После этого ставится точка с запятой.
var x:integer;
означает, что в нашей программе будет использоваться переменная с именем `x`, имеющая тип integer (целое число). Если в программе будет несколько переменных одного типа, то можно до двоеточия перечислить их имена через запятую, а не выписывать отдельную строчку для каждой переменной.
var x,y:integer;
Для описания константы необходимо записать ключевое слово const, затем указать имя константы (отделив его как минимум одним пробелом от ключевого слова), поставить знак равенства и тут же задать её значение, например:
const N=1000;
После окончания описания константы также ставится точка с запятой.
В программе принято сначала описывать константы (если они есть), а уже затем переменные (а они есть практически в любой программе).
Термином «константа» в программировании принято обозначать ещё одно понятие – если в программе встречается некоторое конкретное значение (например, число `1000`), то оно также называется константой соответствующего типа.
Рассмотрим общую структуру программы на языке Pascal. Программа состоит из двух частей: разделов описаний и раздела действий (команд, операторов). Раздел операторов представляет собой некую последовательность команд (операторов), которые должен выполнить компьютер. Другими словами, раздел действий – это собственно программа. В разделах описаний программист сообщает компьютеру, какие объекты он будет использовать в своей программе, сколько памяти под них нужно выделить и т. д.
Из всего перечисленного выше обязательным для каждой программы является лишь раздел действий. Разделов описания в программе может и не быть (хотя в содержательных задачах они будут). Раздел действий начинается с ключевого слова begin, далее записывается список операторов (собственно программа) и в конце пишется ключевое слово end и ставится точка. Внутри списка все операторы отделяются друг от друга специальным символом – точкой с запятой. В этом задании мы познакомимся со следующими операторами:
1) пустой оператор;
2) составной оператор;
3) оператор присваивания;
4) условный оператор;
5) операторы цикла;
6) операторы вывода;
7) операторы ввода.
Начнём с пустого оператора. В языке Pascal пустой оператор – это просто ничего. Он не содержит никаких символов и не выполняет никакого действия. Рассмотрим пример программы состоящей из одного пустого оператора:
begin
end
Эта программа начинается и сразу же заканчивается, не выполняя никаких содержательных действий. Рассмотрим другой пример:
begin
; ; ;
end
Эта программа состоит уже из четырёх пустых операторов, отделённых друг от друга точками с запятой.
Для того чтобы познакомиться с разделами описаний, сначала нужно изучить объекты, которые мы можем использовать в программе.
Изучение любого нового языка всегда начинается с алфавита. В алфавит языка Pascal входят следующие элементы:
1) Заглавные и строчные латинские буквы, символ подчёркивания (по грамматике языка символ подчёркивания считается буквой): _, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z.
2) Цифры: `1, 2, 3, 4, 5, 6, 7, 8, 9, 0`.
3) Знаки операций: + (плюс), – (минус), * (умножить), / (разделить), < (меньше), > (больше), <= (меньше или равно), > = (больше или равно), = (равно), <> (не равно). Последний знак состоит из знаков «меньше» и «больше», записанных без пробелов.
4) Знаки пунктуации, специальные символы:
{ } или (* *) | Скобки комментариев |
[] | Выделение индексов массивов, элементов множеств |
' ' | Выделение символа или строковой константы |
( ) | Выделение выражений, списков параметров |
:= |
Знак оператора присваивания |
; | Разделение операторов и объявлений |
: | Отделение переменной или константы от типа. Отделение метки от оператора |
= |
Отделение имени типа от описания типа. Отделение константы от её значения |
, | Запятая для разделения элементов в списке |
.. | Разделение границ диапазона |
. | Конец программы, отделение целой части от дробной |
# | Обозначение символа по его коду |
В таблице приведены не все знаки пунктуации, а лишь те, которые будут использоваться при дальнейшем изложении.
5) Служебные (ключевые) зарезервированные слова.
Некоторые слова имеют предопределённое значение и используются в качестве элементов при построении сложных конструкций языка. Приведём список зарезервированных служебных слов, которые нам понадобятся в дальнейшем:
and, array, begin, case, const, div, do, downto, end, for, if, mod, not, of, or, program, repeat, string, then, to, type, until, var, while, xor.
Требуется составить программу определения наибольшего общего делителя (НОД) двух натуральных чисел.
Одним из простейших алгоритмов нахождения наибольшего общего делителя является Алгоритм Евклида. Идея этого алгоритма основана на том свойстве, что если `M>N`, то `"НОД"(M, N)="НОД"(M-N, N)`.
Иначе говоря, НОД двух натуральных чисел равен НОД их положительной разности (модуля их разности) и меньшего числа.
var M, N: integer;
begin
writeln('Введите М и N');
readln(M, N);
while M<>N do
begin
if M>N
then M:=M-N else N:=N-M
end;
write('Н0Д=',М)
end.
называется процесс, в котором участвуют две или более стороны, ведущие борьбу за реализацию своих интересов.
Согласно этому определению, довольно много жизненных ситуаций можно считать играми - для этого требуется лишь борьба двух или более лиц и какие-либо интересы, за которые эти лица ведут борьбу. Шахматы, домино, прыжки в высоту - всё это игры. Стремление занять свободное место в автобусе, соперничество мировых держав в ядерной сфере, беседа сотрудника ГИБДД с нарушителем, поход семейной пары в торговый центр - и это тоже игры. Так, в случае стремления занять свободное место в пустом автобусе в этом процессе участвуют не менее двух человек, которые ведут борьбу за свободные места (свои интересы), причём довольно часто количество свободных мест намного меньше количества участвующих в этой игре человек, поэтому в этой игре есть выигравшие и проигравшие. В этом случае интересы (занять свободное место) у игроков совпадают. Однако в случае игры «поход семейной пары в торговый центр» интересы часто строго противоположные: жене хочется совершить как можно больше покупок; мужу - потратить как можно меньше денег на эти покупки.
Изучение такого широкого класса игр математическими методам бессмысленно - в каждой игре есть свои мало-формализуемые особенности, а процесс принятия решений игроками может опираться не только на какие-то математические принципы, в него могут вписываться другие особенности человека, например, уровень интеллекта и характер.
Для решения спора Петя и Вася обращаются к компьютеру за случайным натуральным числом. Если выданное число - чётное, спор выигрывает Петя, если нечетное - спор выигрывает Вася. Является ли описанная процедура игрой?
Данная процедура тоже является игрой - два игрока ведут борьбу за свои интересы (выиграть спор), и то, как это они делают - неважно. Фактически, игроки с помощью компьютера реализовали подкидывание монетки.
Для решения спора Петя и Вася пишут цифры по очереди на доске слева направо, начинает Петя. Если после десяти ходов полученное `10`-значное число не делится на девять, в споре побеждает Петя, а если делится – Вася. Докажите, что Вася может выиграть спор.
Второй игрок (Вася) может дополнять число, написанное первым игроком, до девяти. Если ход Пети - «`9`», то ход Васи - «`0`» и т. п. После десяти ходов получим `10`-значное число, сумма цифр которого равна `9^(**)5=45`, и полученное число будет делиться на девять. Таким образом, второй игрок (Вася) сможет выиграть при любых ходах первого игрока (Пети).
Такие игры, в которых как играть - известно одному или обоим игрокам, уже представляют интерес для формализации и изучения. Одним из самых узких классов таких игр является класс математических игр. Этому классу и посвящено данное задание.
Будем называть игру математической, если для неё выполнены следующие условия:
Условие 1. В игре участвуют два игрока.
Условие 2. Игра заканчиваются выигрышем одного из участников. Это автоматически означает проигрыш соперника. Иногда в математических играх допускают ничью.
Условие 3. В игре участники ходят по очереди и помнят все предыдущие ходы.
Условие 4. Игра характеризуется позицией, которая зависит только от ходов игроков.
Вернёмся к примеру 1. Эта игра не будет являться математической, поскольку не будет удовлетворять только условию 4: мы не сможем определить позицию игры, которая будет зависеть только от хода самих игроков, поскольку игроки обращаются к компьютеру.
Также в математических играх по той же причине не может быть случайных карточных раскладов, игральных кубиков, подкидываний монеток. Попробуем же тогда реализовать игру из этого примера, которая является фактическим подкидыванием монетки игроками, без помощи, как монетки, так и компьютера.
Для решения спора Петя и Вася пишут на листочках по натуральному числу. Если сумма написанных чисел - чётная, спор выигрывает Петя, если нечетная - спор выигрывает Вася. Является ли описанная процедура математической игрой?
Здесь уже не выполняется условие 3, которое гласило, что игроки должны ходить по очереди и помнить все предыдущие ходы.
Сделаем небольшую модификацию условий игры, чтобы игра стала математической и посмотрим, какая игра из этого получится. Чтобы условие 3 поочередности выполнялось, сначала должен походить первый игрок, написать своё число на бумажке и показать это число всем, включая второго игрока. Кто из двух игроков будет первым, они между собой должны договориться сами. И тогда уже второй игрок, зная число, которое написал первый, должен написать своё число, затем эти два числа будут сложены и сумма проверена на чётность.
Однако, если второй игрок обладает хоть каким-либо интеллектом, он может подобрать своё число, чтобы сумма была выигрышной для него чётности. Суть «подкидывания монетки» от этого полностью теряется, т. к. данная игра находится под полным контролем второго игрока.
Два человека встречаются и обмениваются закрытыми сумками, понимая, что одна из них содержит деньги, другая - товар. Каждый игрок может уважать сделку и положить в сумку то, о чём договорились, либо обмануть партнёра, дав пустую сумку. Является ли эта игра математической?
Во-первых, эта игра не удовлетворяет условию 2: в условии не определено, какой игрок выигрывает в каком случае, а какой автоматически при этом проигрывает. Во-вторых, игроки ходят одновременно, а не по очереди, что нарушает условие 3. Поэтому данная игра не является математической.
Заметим, что условие 2 можно выполнить, считая, что в случае если один игрок обманул другого, обманувший игрок выиграл, а обманутый проиграл, в остальных случаях (оба игрока честные или оба обманщики) зафиксировать ничью. Однако условие 3, как и в предыдущем примере, уже нельзя выполнить без существенного изменения самой игры.
Итак, в математической игре имеются два игрока, которые ходят поочередно. Участник, который начинает игру, обычно называется первым игроком, его соперник – вторым. Имеется конечное или бесконечное множество позиций. В каждой позиции для обоих игроков указаны допустимые ходы – разрешённые переходы в другие позиции. Некоторые позиции объявляются выигрышными для какого-то игрока, что автоматически означает, что эти позиции являются проигрышными для соперника. Очень часто выигрышными объявляются те и только те позиции, из которых соперник не может сделать ход, т. е. выигрывает тот игрок, которому удаётся своим последним ходом достичь позиции, в которой у соперника нет допустимых ходов.
Есть две кучи по семь камней в каждой. За ход разрешается взять любое количество камней, но только из одной кучи. Проигрывает тот, кто не сможет сделать ход. Как можно определить позиции в данной игре, и какие позиции будут выигрышными?
Позицией в данной игре являются два числа `(x, y):` `x` – количество камней в первой куче, `y` – количество камней во второй куче. Игрок выигрывает, если противник не может сделать ход, т. е. перед ходом противника камней в обеих кучах не останется. Таким образом, позиция `(0, 0)` является выигрышной для того из игроков, который попал туда своим последним ходом.
Особенно отметим следующее.
Во-первых, в играх могут быть ничьи. Это значит, что некоторые позиции для обоих игроков объявляются ничейными. Игроку целесообразно добиваться ничьей только тогда, когда он не может гарантированно достичь выигрышной позиции.
Во-вторых, оба игрока не обязательно должны преследовать одинаковые цели (например, чтобы противник не смог сделать ход). Так, например, в примере 2 один из игроков стремится к тому, чтобы полученное число не делилось на девять, а второй стремится к обратному.
Поэтому позиция должна ещё характеризоваться номером игрока (либо того, который пришел в эту позицию, либо того, который делает ход из этой позиции в зависимости от ситуации). Так, если в примере 7 добавить номер игрока, который делает ход, то теперь позиция в этой задаче будет выражаться тремя числами `(x,y,n)`, где `n` – номер игрока, который делает ход, имея в начале $$ x$$ камней в первой куче, а $$ y$$ – во второй.
Позиция `(0,0,1)` будет проигрышной для первого игрока (он не может сделать ход) и выигрышной для второго, позиция `(0,0,2)` – наоборот.
Однако в играх, в которых игроки преследуют одинаковые цели и возможные ходы у обоих игроков одинаковы, как например, в примере 5, можно номер игрока из позиции опустить. В этом задании мы будем рассматривать только такие игры.
В точке `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)` игроки не сделают ни одного хода, и немедленно будет объявлен результат.
Вернёмся к примеру 5 и зададимся вопросом: кто выиграет?
В общем случае может выиграть любой из игроков – для этого его сопернику достаточно «подыграть». Однако второй игрок может выиграть при любых ходах первого игрока. Для этого ему нужно брать то же количество камней, которое брал первый игрок предыдущим ходом, но из другой кучи. После хода второго игрока количество камней в обеих кучах будет равным. Далее. Первый игрок возьмёт несколько камней в одной из кучек, тогда после его хода количество камней в кучках станет неодинаковым, а значит, второй игрок сможет уравнять количество камней в кучах и передать ход сопернику. Второй игрок всегда сможет сделать свой ход, а поскольку камней становится все меньше и меньше, наступит момент, когда один из игроков не сможет сделать ход, и это будет первый игрок. Таким образом, второй игрок сможет выиграть в данный игре, как бы ни играл первый.
Выигрышной стратегией назовём набор правил, следуя которым, один из игроков обязательно выиграет при произвольных ответах соперника.
Аналогично, ничейной стратегией назовём набор правил, следуя которым, один из игроков обязательно выиграет или сведёт игру к ничьей при произвольных ответах соперника.
Подчеркнём в определении стратегии условие «при произвольных ответах соперника». Важно понимать, что на месте игрока может оказаться что или кто угодно, например, компьютер. Нужно уметь отвечать на произвольные ходы соперника и в любом случае выигрывать.
Как было сказано выше, мы пытались выделить игры, в которых один из игроков обязательно выиграет при произвольных ответах соперника. Следующая теорема позволяет утверждать, что математические игры и есть искомый класс игр.
В любой математической игре существует либо выигрышная стратегия одного из игроков, либо ничейная стратегия для обоих игроков.
Идея доказательства этого утверждения в частном случае будет рассмотрена при решении задач методом анализа с конца (см. § 3).
С одной стороны, заметим, что данная теорема обобщается на случай игр, которые теоретически могут продолжаться бесконечно долго. Для этого в условии теоремы вместо существования ничейной стратегии для обоих игроков нужно потребовать, чтобы каждый игрок имел стратегию, позволяющую данному игроку не проиграть.
С другой стороны, рассмотрим игры, которые завершаются за конечное количество ходов выигрышем одного из игроков (и ничьих нет). Согласно теореме, у кого-то из игроков обязательно существует выигрышная стратегия, и он должен выиграть у своего соперника, как бы ни играл последний. Введём понятие правильной игры.
называется игра, в которой каждый из игроков применяет выигрышную или ничейную стратегию, если она у него есть.
Так, если игроки из примера 2 играют в правильную игру, второй игрок должен воспользоваться своей выигрышной стратегией (например, дополнять число до девяти; у него может быть также и иная выигрышная стратегия) и довести игру до победы.
Таким образом, ответить на вопрос, заданный в самом начале (см. пример 1), кто выиграет при правильной игре, можно так: необходимо найти определённую стратегию одного из игроков и доказать, что она является выигрышной.
В заключение параграфа отметим, что согласно теореме выигрышная или ничейная стратегия существуют даже в таких математических играх, как шахматы и шашки. Однако ни человеческий ум, ни современные вычислительные мощности пока не позволили найти эту стратегию…
Одним из способов нахождения выигрышных стратегий является удачный ответ на ход противника, например, учитывающий симметрию.
Два игрока по очереди ставят на шахматную доску слонов так, чтобы фигуры не били друг друга. Цвет фигур значения не имеет. Проигрывает тот, кто не сможет сделать ход. Кто выиграет при правильной игре?
Выиграет второй игрок. Для этого мысленно разрежем шахматную доску пополам линией, параллельной одной из сторон доски. Второй игрок должен ставить слона на место, симметричное полю, на которое текущим ходом поставил свою ладью первый игрок относительно проведённой оси. Докажем от противного, что второй игрок всегда сможет сделать ход.
Пусть это неверно и второй игрок не сможет сделать хода. Разберём два случая.
Случай 1. На поле предполагаемого хода уже стоит слон. Но этот слон не мог быть поставлен ранее вторым игроком, так как он ставит слонов только симметрично ходам первого игрока. Если первый игрок ранее поставил слона на это поле, то второй игрок был обязан своим ходом поставить слона на поле, симметричное полю противника. Однако по условию на это поле слона поставил первый игрок текущим ходом. Получаем противоречие.
Случай 2. Данное поле находится под боем какого-то слона. Заметим, что этот слон не был поставлен первым игроком на предыдущем ходу, так как два симметричных относительно оси слона не бьют друг друга. Тогда, в соответствии со стратегией второго игрока, слон, расположенный симметрично данному, также должен уже стоять на доске. Однако этот слон будет бить слона, поставленного первым игроком предыдущим ходом. Противоречие.
Таким образом, было доказано, что у второго игрока всегда есть допустимый ход, а так как игра должна когда-нибудь закончиться (на шахматной доске всего 64 клетки), то первый игрок когда-то не сможет сделать своего хода и проиграет.
В кучке лежат: а) `30` камней; б) `32` камня. За ход можно взять от одного до пяти камней из кучи. Проигрывает тот, кто не сможет сделать ход. Кто выигрывает при правильной игре?
В данном случае работает стратегия дополнения до шести. Пусть своим ходом первый игрок берёт `x in{1,2,3,4,5}` камней. Тогда в пункте а) второй игрок отвечает ходом `(6-x)`, и поскольку после каждого его хода количество камней будет делиться на шесть, то в итоге второй игрок выиграет.
В пункте б) выигрывает первый игрок. Первым ходом он должен взять два камня и свести задачу к пункту а), в котором он уже будет выступать как второй игрок.
Два игрока перемещают ладью из левого нижнего угла `("a"1)` шахматной доски в правый верхний `("h"8)`. За ход можно сместить ладью на любое количество клеток вверх или вправо. Кто выиграет при правильной игре?
Выиграет второй игрок. Для этого ему нужно во время ходов возвращать ладью на диагональ, проведенную из левого нижнего угла в верхний правый угол. Подумайте, почему первый игрок проиграет при любых своих ходах.
Вторым важным способом решения задач является решение задачи с конца. Предположим (хотя это и не всегда верно), что для обоих игроков одни и те же позиции являются выигрышными.
Вернёмся к примеру 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 в частном случае игр, в которых ничейных позиций нет, и каждая позиция является выигрышной для одного из игроков.
Два игрока играют в следующую игру. Перед ними лежат две кучи камней, в первой – три камня, а во второй – два камня. У каждого игрока имеется неограниченно много камней. Игроки ходят по очереди. Ход состоит в том, что игрок или увеличивает в три раза число камней в какой-либо куче, или добавляет один камень в любую кучу. Выигрывает тот игрок, после хода которого, в двух кучах станет не менее `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)` был поставлен знак «`+`», а это значит, что победит второй игрок.
При оформлении задачи необходимо указать выигрывающего игрока, записать его стратегию и показать, что этот игрок победит при любых ответах соперника. Если имеется таблица позиций, то стратегия выигрывающего игрока формулируется простым правилом: делать ходы в позиции, отмеченные знаком «`+`». Но эту стратегию рекомендуется записать в явном виде. Таблицу позиций же, наоборот, при оформлении работы можно не рисовать (она уже сделала свое дело: помогла определить победителя и найти его стратегию).
Покажем, что второй игрок может выиграть при произвольных ответах первого игрока.
Рассмотрим все возможные начальные ходы первого игрока и укажем правильные ответы соперника:
а) если первый игрок в три раза увеличивает число камней в одной из куч, то второй игрок должен увеличить количество камней в этой же куче также в три раза. Тогда в обеих кучах будет как минимум 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` камней, нас не интересуют, так как все эти позиции являются выигрышными.
Данный способ является разновидностью анализа с конца и заключается в том, что мы будем анализировать в знаках «`+`» и «`-`» не все позиции, а только те, в которые можно прийти из начальной позиции. Для этого мы нарисуем дерево ходов из начальной позиции. Разберём этот метод на примере 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] Поскольку данное дерево игры заполнялось знаками по тем же правилам, что и таблица позиций, знаки «`+`» и «`-`» в позициях, отмеченных на дереве и в таблице позиций ранее, должны совпадать.
Данный параграф появился в связи с тем, что с 2015 года в ЕГЭ в задаче по теме теории игр требуется не только указать стратегию выигравшего, но и провести более подробный анализ, нарисовав дерево игры (о чём прямо сказано в условии) и ответив на дополнительные вопросы вида «из каких позиций выиграет первый игрок, причем ровно за два хода» или «какое максимальное количество ходов потребуется для выигрыша». Условие такой задачи в реальном ЕГЭ будет, скорее всего, очень длинным и занимать до страницы; однако этого не нужно бояться.
Два игрока играют в следующую игру. Перед игроками лежит куча из `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)`.
Два игрока играют в следующую игру. Перед игроками лежит две кучи: в первой куче `5` камней, во второй куче - `S` камней. Игроки по очереди могут за ход провести над одной из куч следующую операцию: добавить `2` камня в кучу или, если количество камней в куче чётно, увеличить количество камней в куче в `2,5` раза. Выиграет игрок, после чьего хода суммарное количество камней в обеих кучах будет не менее `39`.
Укажите все значения `S`, при которых в правильной игре
А) Первый игрок может выиграть первым ходом
Б) Второй игрок может выиграть первым ходом.
В) Первый игрок может выиграть вторым ходом, но не может выиграть первым ходом.
Данный пример очень похож по условию на пример 11, однако здесь возникает проблема в том, что количество куч - две, хоть и задано, что изначально в первой куче `5` камней. Если рассматривать двумерную таблицу позиций, это приведёт к побочному анализу многих позиций, в которых количество камней в первой куче отлично от пяти (в примере 11 таких «лишних» позиций не было). Построение дерева игры также не приведёт к быстрому результату, т. к. начальная позиция неизвестна, и такие деревья нужно будет рисовать при каждом `S`.
В связи с этим проведём предварительный анализ игры, не пользуясь ни таблицей позиций, ни деревом игры.
А) Первый игрок выиграет за ход. Первый игрок не может увеличить количество камней в кучке из `5` камней в `2,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) чётно.