Анализируем материалы ЕГЭ по информатике, предложенные Федеральной службой по надзору в сфере образования и науки Российской Федерации в демонстрационном варианте от 2009 г.

Попробуем предугадать оценку выпускника 2009 года, решившегося сдать информатику в формате ЕГЭ. Для этого проанализируем несколько заданий из «творческой части» демонстрационных материалов, опубликованных в Интернете Федеральной службой по надзору в сфере образования и науки Российской Федерации.

Часть 3. Задача С1. Чтобы не ошибиться в постановке условия задачи, взглянем на скриншот оригинала:

Скриншот задани ЕГЭ 2009

Предвидеть действия большей части экзаменующихся, столкнувшихся с такой постановкой задачи несложно, поскольку даже начинающий изучать Бейсик, не вникая в суть алгоритма, найдет сразу же три ошибки, так как он твердо знает, что ENDIF следует писать раздельно.
Выпускнику и невдомек, что «программист - торопыга» «желал сделать всего две ошибки», а допустил их - целых пять, поэтому он (ученик), просидевший уже полтора часа над частью А и Б, скорее всего, не заметит «подвоха» и не станет анализировать алгоритм, а быстренько перепишет приведенный выше код, исправив трижды ENDIF на END IF, затем приведет пример чисел x,y, при которых программа будет неверно решать поставленную задачу и перейдет ко второму вопросу.
И что же при этом получит за «свою опрометчивость» наш испытуемый? Взглянем на критерии оценивания, откуда «ясно видно», что замеченные и исправленные им три ошибки не будут оценены никоим образом.

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

Возникает извечный вопрос: "Кто виноват?"

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

Если бы не было "...программист торопился и написал программу неправильно" и самой программы, то стало бы ясно, что нужно проанализировать два интервала: 0<=X<=3,14/2 и SIN(X)<=Y<=1 которые можно записать однозначно: (0<=X) AND (X<=3.14/2) AND (SIN(X)<=Y) AND (Y<=1) откуда вытекают сами собой правильные решения, например такие:

a$="принадлежит"
INPUT X,Y
IF (0>X) AND (X>3.14/2) AND (SIN(X)>Y) AND (Y>1) THEN a$="не принадлежит"
PRINT a$

либо такое:

a$="принадлежит"
INPUT X,Y
IF (0>X) AND (X>3.14/2) AND (SIN(X)>Y) AND (Y>1) THEN a$="не "+a$
PRINT a$

или наоборот:

a$="не принадлежит"
INPUT X,Y
IF (0<=X) AND (X<=3.14/2) AND (SIN(X)<=Y) AND (Y<=1) THEN a$="принадлежит"
PRINT a$

и совсем банальное решение:

INPUT X,Y
IF (0<=X) AND (X<=3.14/2) AND (SIN(X)<=Y) AND (Y<=1) THEN
_____PRINT "принадлежит"
ELSE
_____PRINT "не принадлежит"
END IF

и алгоритм представленный ниже тоже отработает правильно

INPUT X,Y
S=3.14/2
Q=SIN(X)
IF 0>X AND X>S AND Q >Y AND Y>1 THEN
_____PRINT "не принадлежит"
ELSE
_____PRINT "принадлежит"
END IF

Но пойдем дальше, предложив экзаменуемому выступить в роли эксперта и проверить на работоспособность предложенный алгоритм задачи С2, вот ее условие:

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

Скриншот предлагаемого решения

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

Реакция исполнителя показана на скриншоте ниже:

Сриншот, демонстрирующий реакцию исполнителя на предложенный алгоритм в качестве правильного решения составителями задания для ЕГЭ

Исправить положение в этой ситуации конечно несложно, достаточно три первых строки опустить ниже этой -
DIM I, A(N), B(N) AS INTEGER (ее можно вообще удалить), а строку с командой N=30 переместить на самый верх, и алгоритм заработает должным образом.

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

В качестве примера можно привести большое количество алгоритмов, вполне работоспособных в Бейсике, но абсолютно неприемлемых в Паскале. Вот некоторые из них:

INPUT N
DIM A(N), B(N)
___FOR I = 1 TO N : INPUT A(I) : NEXT
______FOR I = 1 TO N
_________IF A(I)<0 THEN
____________B(I) = SQR(A(I)^2)
_________ELSE
____________B(I)=A(I)
_________END IF
______NEXT
END

INPUT N
DIM A(N), B(N)
FOR I = 1 TO N : INPUT A(I) : NEXT
___FOR I =1 TO N
_____B(I) = A(I):IF B(I) < 0 THEN B(I) = (B(I)*B(i))^(1/2)
___NEXT
END

Или даже вот такой, на первый взгляд совсем несуразный однострочный код:

FOR I=1 TO 30:INPUT A(I):B(I)=A(I):IF B(I)<0 THEN B(I)=(B(I)^2)^(1/2):PRINT A(I),B(I):NEXT

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

Отсюда вытекает еще один вопрос: "Какой алгоритм считать за правильный: тот, который понимает и выполняет соответствующий исполнитель, или тот, который попадает под перечисленные исключения, надуманные, но, возможно, не всегда додуманные составителями задач?"

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

const N=10;
var a,b:array[1..N] of integer;
i: integer;
begin
for i:=1 to N do read(a[i]);
for i:=1 to N do
if a[i]<0
then b[i]:=-a[i]
else b[i]:=a[i];
end.

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

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

const N=10;
var a,b:array[1..N] of integer;
i: integer;
begin
for i:=1 to N do read(a[i]);
for i:=1 to N do
if a[i]<0
then b[i]:=-a[i]
else b[i]:=a[i];

end.

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

const N=10;
var a,b:array[1..N] of integer;
i: integer;
begin
___for i:=1 to N do read(a[i]);
______for i:=1 to N do
________if a[i]<0 then b[i]:=-a[i]
_______________else b[i]:=a[i];
___for i:=1 to N do writeln(‘a[‘, i ,’] = ’, a[i], ‘ b[‘, i ,’] = ’,b[i]);

end.

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

за решение ставятся 2 балла, если:

1. Предложен правильный алгоритм, выдающий верное значение.
2. Возможно использование числа 30 вместо константы.
3. Возможно использование операции «больше» (так как -0=0)
4. Возможно использование двух индексных переменных.
5. Возможно наличие отдельных синтаксических ошибок…
6. Алгоритм может не содержать ввода-вывода данных.
7. Алгоритм может не содержать объявления массивов.

В принципе, все понятно за исключением нескольких моментов:

А если, согласно пункта 3, разрешающего использование операции «больше», воспользоваться знаком «умножение», или « возведение в степень», или «корень квадратный», то что? «Незачет»?

А при выполнении пункта 7, заодно не объявить и индексную переменную, то что? Решение опять неверное?

Пункт 5 тоже кроет в себе большое количество вопросов, начиная от каких именно и заканчивая - сколько таковых может быть?

Пункт 4 разрешает использовать две индексных переменных, а почему их не может быть три?

Вдруг найдутся «оригиналы-максималисты или минималисты», не терпящие «шаблонов» и выдадут вот такие решения, разложив все по «полочкам» или наоборот, «свалив все в одну кучу»:

for i:=1 to N do read(a[i]); ________________ _______ {задаем целочисленный массив}
___for k:=1 to N do a[k]:=b[k];_____________________{создаем из заданного новый}
_____for j:=1 to N do if b[j]<0 then b[j]:=sqrt(sqr(b[j]));_{прокручивая новый, избавляемся от знаков «-«}

Можно согласиться с тем, что решение не рационально, но оно не является неправильным, поскольку приводит к «выдаче верного значения», стало быть, 2 балла обеспечено. Не спешите.

Увы! Нарушен пункт 4, так как задействовано ЦЕЛЫХ ТРИ индексных переменных, кроме того, кто разрешил использовать sqrt и sqr и, наконец, кто позволил разделить по разным циклам создание второго массива и его проверку на отрицательность.

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

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

А этому «оригиналу-минималисту» просто нравятся решения, сжатые до неузнаваемости, попробуйте оценить его работу, опираясь на все семь пунктов: Исполнитель Бейсика допускает даже такой вариант:

FOR i=1 TO N : INPUT a(i) : b(i) = a(i) : IF b(i) < 0 then b(i) = b(i)*-1 : NEXT

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

For i:=1 to N do
___begin
_____b([i]):=a([i]); if b([i])<0 then b([i]):=b([i])*-1;
___end;

его Паскаль отработает правильно, хотя и смотрится он не совсем логично. Но давайте попробуем проговорить данный алгоритм. “В цикле последовательно заносим элементы предложенного вектора в новый, после чего сравниваем его с нулем, и если новый элемент окажется меньше нуля, то умножаем его на «-1»

Вроде все логично! Кстати, а кто сказал, что знак можно поменять только таким образом, как мы это делали до сих пор:

if b([i])<0 then b([i]):=b([i])*-1; или if b([i])<0 then b([i]):=-b([i]);

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

IF b(i)<0 THEN b(i) = SQR(b(i)*b(i))

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

if b[i] < 0 then b[i]:= sqrt(sqr(b[i]));

Проверяющему бы этого энтузиаста поощрить за столь нестандартный подход, но, увы и ах, инструкция допускает использование только знака «больше», а про извлечение из-под корня квадратного произведения отрицательного числа самого на себя там ничего не говорится, а стало быть, алгоритм можно посчитать за "неправильный" и его решение будет оценено - 0

А нами тем временем уже найдено четыре способа избавления от знака «минус» без использования специальной функции ABS( ) на Паскале:

1) b[i]:=-b[i];___ 2) b[i]:=b([i])*-1; ___ 3) b(i):= sqrt(sqr(b[i]));___ 4) b(i):= sqrt(b[i]*b[i]);

на Бейсике таковых можно записать пять:

1) b(i)=-b(i) 2) b(i)=b(i)*-1 3) b(i)= SQR(b(i)*b(i)) 4) b(i)= SQR(b(i)^2) 5) b(i)=(b(i)^2)^1/2

Можно подумать, что на этом все возможности замены знака минус закончились, но не тут -то было. Например, и такое решение: b(i) = (b(i)/b(i))^2 ведет к правильному завершению алгоритма, но здесь главное не переусердствовать, поскольку следующая запись: b(i) = (b(i)^2)/b(i) сработает неверно.

Проанализируем запись b(i)^2 – она означает возведение в степень. Стало быть, это выражение эквивалентно следующему exp(2*ln(b(i))), что творческому человеку может дать толчок к поиску новых сотен вариантов решений. Но мы не будем их исследовать, а оттолкнемся от ранее предложенного алгоритма составителями задачи и поищем иные способы реализации правильных алгоритмов.

Итак, мы имеем: if a[i]<0 then b[i]:=-a[i]
____________________else b[i]:=a[i];

А если проделать следующий трюк: if a[i]>0 then b[i]:=a[i]
___________________________________else b(i):=sqrt(sqr(b[i]));

то становится понятно, о чем говорится в пункте 3 насчет операции «больше», но при этом совершенно непонятно, а почему нельзя использовать иные математические операции, например «больше либо равно», «меньше либо равно», «*», «/» или функции Sqrt(), Sqr() и так далее.

Вероятно, к тому же результату приведут и алгоритмы:

if a[i]<=0 then b[i]:=-a[i]
_______else b[i]:=a[i];

if a[i]>=0 then b[i]:=a[i]
_______else b[i]:=-a[i];

b[i]:=a[i];
if a[i]<0 then b[i]:=-a[i];

b[i]:=a[i];
if b[i]<0 then b[i]:=b[i]*-1;

все варианты работают верно! Сколько модификаций мы имеем – шесть, пусть незначительных, но все же. А теперь к каждому из этих шести применим по одному из предложенных выше, сколько получим… Возможны решения поставленной задачи и другими путями, например, тот же результат мы получим, если вместо инструкции for воспользуемся инструкциями repeat, while и даже goto. Рассмотрим в качестве вариантов и такие решения.

i:=1;
repeat
begin
__if a[i]<0 then b[i]:=a[i]*-1
________else b[i]:=a[i];
i:=i+1;
end;
until i>N;

i:=1;
while i<=N do
begin
__if a[i]<0 then b[i]:=a[i]*-1
________else b[i]:=a[i];
i:=i+1;
end;

i:=1;
povtor:
__if a[i]<0 then b[i]:=a[i]*-1
________else b[i]:=a[i];
i:=i+1;
if i<=N then GoTo povtor;

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

label povtor;
const N=10;
var a,b:array[1..n] of integer;
i: integer;
begin
____for i:=1 to N do read(a[i]);
____i:=1;
____povtor:
______if a[i]<0 then b[i]:=a[i]*-1
____________else b[i]:=a[i];
______i:=i+1;
______if i<=N then GoTo povtor;
end.

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

Но такое вмешательство не проходит даром, и алгоритм перестает правильно работать. А что нам может помешать его подправить? В связи с чем меняем строку:

for i:=1 to N do read(a[i]); i:=1; на: for i:=N downto 1 do read(a[i]);

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

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

label povtor; const N=10;
var a,b:array[1..n] of integer; i: integer;
begin
____for i:=N downto 1 do read(a[i]);
____povtor: if a[i]<0 then b[i]:=-a[i]
________________else b[i]:=a[i];
__________i:=i+1; if i<=N then GoTo povtor;
end.

Либо так:

var a,b:array[1..30] of integer; i: integer;
begin
____for i:=30 downto 1 do read(a[i]);
_____while i<=30 do
______begin
_________if a[i]<0 then b[i]:=a[i]*-1
_______________else b[i]:=a[i];
_________i:=i+1;
______end;
end.

А вот еще несколько вариантов:

label povtor; const N=10;
var a,b:array[1..n] of integer;i: integer;
begin
for i:=N downto 1 do read(a[i]);
________povtor: b[i]:=a[i]; if a[i]<0 then b[i]:=a[i]*-1;
_____________ i:=i+1; if i<=N then GoTo povtor;
end.

label povtor;
const N=10;
var a,b:array[1..n] of integer;
i: integer;
begin
for i:=N downto 1 do read(a[i]);
________povtor: b[i]:=a[i]; if b[i]<0 then b[i]:=b[i]*-1;i:=i+1;
______________if i<=N then GoTo povtor;
end.

const N=10;
var a,b:array[1..n] of integer;i: integer;
begin
for i:=N downto 1 do read(a[i]);
_________while i<=N do
____________begin
______________b[i]:=a[i]; i:=i+1;
______________if a[i]<0 then b[i]:=a[i]*-1;
____________end;
end.

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

Например, если в описаниях по Паскалю можно найти две инструкции: While и REPEAT, то Бейсик таковых имеет четыре, вот они:

DO WHILE условие_______________________DO UNTIL условие
_____тело цикла____________________________тело цикла
LOOP _________________________________LOOP

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

DO WHILE ______________________________DO UNTIL
_____тело цикла_____________________________тело цикла
LOOP условие___________________________ LOOP условие

Кроме того, если в Паскале извлечь из квадратного корня можно только так: sqrt(a) то в Бейсике таких возможностей две: sqr(a) и a^(1/2) и т.д.

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

Отсюда возникают новые вопросы:

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

Если да, то почему Федеральная служба по надзору в сфере образования и науки Российской Федерации не увидела в демонстрационном варианте от 2009 г., что предлагает неработоспособные алгоритмы всего в одном варианте?

И еще:

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

Рейтинг@Mail.ru

Copyright © А.Козлов, 2009