I Областная олимпиада школьников по информатике

2016-2017 учебный год

9-10 классы

Заключительный тур

Разбор задач

Задача 1. Игра на вылет (10 баллов)

Ответ для каждого входного значения N равен log2(N) c округлением вверх. Или, другими словами, это максимальная степень числа 2, больше или равная N. Обоснуем это.

Вначале рассмотрим частный случай, когда N − это степень двойки. Для удобства будем считать, что все матчи разбиты на туры. В первом туре все команды делятся на пары и играют друг с другом, в результате половина команд выбывает. Во втором туре повторяется то же самое, и так до тех пор, пока не останется одна команда. Количество туров равно количеству раз, которое N нужно поделить на 2, чтобы получить 1 − то есть log2(N). Понятно, что количество туров − это и есть максимальное количество матчей, в которых может сыграть одна команда.

Если N не равняется степени двойки, то схема разбиения на туры несколько меняется. Если N − нечётное, то одна команда в текущем туре не играет, а сразу переходит в следующий тур, в котором она сыграет в первом же матче. То есть, если в предыдущем туре было N команд, то в следующем туре их будет N/2 с округлением вверх. Чтобы сосчитать количество туров, можно просто промоделировать этот процесс. Нетрудно также заметить (и при желании доказать), что количество туров будет равно log2(N) с округлением вверх.

Осталось еще показать, что для любого N всегда найдётся такое расписание, что хотя бы одна команда поучаствует в матче в каждом туре. Пусть это будет команда №1. Будем считать, что если какая-то команда не участвовала в предыдущем туре, то в следующем она обязательно проигрывает. Тогда в каждом следующем туре будут участвовать команды, которые уже сыграли одинаковое число матчей, за исключением, возможно, одной команды, которая сыграла на один матч меньше. Отсюда следует, что расписание матчей в каждом туре всегда можно назначить так, чтобы команда №1 с кем-то играла.

НЕ нашли? Не то? Что вы ищете?

Заметим, что здесь нужно быть аккуратным с вещественными вычислениями. Например, log2(1073741825)≈30.000000001, и округление вверх даёт 31, а не 30.

Задача 2. Сумма (10 баллов)

Возможным ответом на эту задачу является следующая формула:

(A/2*2 + (B-1)/2*2 + 2) / 2 * ((B-1)/2 - A/2 + 1)

Для её вывода воспользуемся формулой суммы арифметической прогрессии:

S = (x1+xn)/2*n, где x1 и xn − первый и последний член последовательности, n − количество членов.

Однако, напрямую применить эту формулу затруднительно, поскольку значения A и B могут быть как нечётными, так и чётными.

Получим из A нечётное число − первый член последовательности. Несложно убедиться, что выражение x1 = A/2*2+1 даёт требуемый результат. Например, 4/2*2+1=5, 5/2*2+1=5.

Теперь получим из B нечётное число − последний член последовательности. Требуемый результат даёт выражение xn = (B−1)/2*2+1. Например, (7−1)/2*2+1=7, (8−1)/2*2+1=7.

Количество нечётных чисел между двумя нечётными числами x1 и xn найдётся как = (xn − x1 + 2) / 2. Подставим выражения для x1 и xn и сократив, получим n = (B-1)/2−A/2+1.

Подставив в формулу для суммы арифметической прогрессии выражения для x1, xn и n и сократив, получаем, что сумма равна:

(A/2*2 + (B-1)/2*2+2) / 2 * ((B-1)/2-A/2+1)

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

(A/2*2 + (B-1)/2*2+2) * ((B-1)/2-A/2+1) / 2

Разумеется, верная формула может быть записана разными способами, поэтому проверка решений этой задачи выполняется путём подстановки ряда значений A и B в решение участника и сравнением результата с правильным. Например, вот ещё один верный ответ:

((A+1-A%2) + (B-1+B%2)) * (((B-1+B%2) - (A+1-A%2)) / 2 + 1) / 2

Задача 3. Пилообразные числа (10 баллов)

Данную задачу можно решить методом динамического программирования. Создадим две матрицы g и l размерности 1..N по вертикали и 0..9 по горизонтали, где:

·  g[i][j] − это количество пилообразных последовательностей длины i,которые кончаются цифрой j, а предыдущая цифра больше j.

·  l[i][j] − это количество пилообразных последовательностей длины i,которые кончаются цифрой j, а предыдущая цифра меньше j.

Теперь несложно написать правила перехода от i к (i+1).

Для всех пар цифр k и j, где k > j:

l[i + 1][k] := l[i + 1][k] + g[i][j]

Для всех пар цифр k и j, где k < j:

l[i + 1][k] := l[i + 1][k] + g[i][j]

В первой формуле мы говорим, что можно добавить цифру k в конец всех i-разрядных чисел, которые кончались цифрой j, а предыдущая цифра была больше j. Поскольку теперь предыдущая цифра станет меньше k, то результат добавляется к элементу матрицы l. Вторая формула объясняется аналогично.

Заметим, что ответ может диапазон значений 32-битного целого типа, поэтому нужно использовать 64-битный тип.

Пример решения на Free Pascal:

const NMAX = 18;

var

g, l: array[1..NMAX, 0..9] of Int64;

n, i, j, k: integer;

ans: int64;

begin

fillchar(g, sizeof(g), 0);

fillchar(l, sizeof(l), 0);

read(n);

for i := 1 to 9 do begin

g[1][i] := 1;

l[1][i] := 1;

end;

for i := 1 to n - 1 do

for j := 0 to 9 do begin

for k := j + 1 to 9 do

l[i + 1][k] := l[i + 1][k] + g[i][j];

for k := 0 to j - 1 do

g[i + 1][k] := g[i + 1][k] + l[i][j];

end;

ans := 0;

for j := 0 to 9 do

ans := ans + l[n][j] + g[n][j];

writeln(ans);

end.

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

Подзадача 2 может быть решена преподсчётом. Например, меньше, чем за минуту (на Intel Core i5) можно перебрать все 12-разрядные пилообразные числа с помощью перебора с возвратом:

const

UP = 1; DOWN = 2;

var

n, i: integer;

ans: int64;

procedure backtrack(prev, n, dir: integer);

var

d: integer;

begin

if dir = UP then begin

for d := prev + 1 to 9 do

if n > 1 then

backtrack(d, n - 1, DOWN)

else inc (ans);

end else begin

for d := 0 to prev - 1 do

if n > 1 then

backtrack(d, n - 1, UP)

else

inc(ans);

end;

end;

begin

assign(input, 'input. txt');

reset(input);

assign(output, 'output. txt');

rewrite(output);

read(n);

ans := 0;

for i := 1 to 9 do begin

backtrack(i, n - 1, UP);

backtrack(i, n - 1, DOWN);

end;

writeln(ans);

end.

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

Задача 4. Лабиринт (10 баллов)

Задача решается методом поиска в ширину (bfs). Создадим следующие структуры данных:

·  матрица field для хранения входного игрового поля;

·  очередь queue, в которую будут помещаться записи со следующими четырьмя полями: координаты клетки x и y, номер игрока player, длина кратчайшего пути до этой клетки pathLength;

·  трёхмерный массив visited, в котором для каждой клетки и каждого игрока будет отмечаться, что он её уже посетил;

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

Вначале занесём в очередь начальные координаты всех игроков. В массиве visited отметим, что эти клетки ими уже посещены. В матрице playersCount поставим в эти клетки по единичке.

Далее повторяем в цикле следующие действия. Извлекаем из очереди очередной элемент. Пытаемся сходить влево, вправо, вверх и вниз. Если в соответствующей клетке нет стены, и мы там ещё не бывали (смотрим в visited), то делаем следующее:

·  заносим её координаты в очередь (при этом поле pathLength увеличится на 1),

·  отмечаем посещённость клетки в массиве visited,

·  в матрице playersCount увеличиваем число игроков для этой клетки на 1.

Процесс заканчивается либо когда очередь опустеет, либо когда в матрице playersCount впервые появится значение, больше или равное K − требуемому числу игроков.

Количество итераций цикла будет равно количеству элементов, добавляемых в очередь. Поскольку каждая тройка <y, x, игрок> может быть добавлена в очередь только один раз, то количество итераций не превышает MNU (где U − количество игроков на поле). При максимальных ограничениях задачи это будет 200∙200∙100= 4 000 000.

Оценим ещё требования к памяти (хотя на олимпиаде она существенно и не ограничивалась). Если для целых чисел использовать 2-байтный тип, то под очередь нужно выделить 200∙200∙100∙8≈32 мегабайта. Под массив visited нужно 200∙200∙100≈4 мегабайта, плюс ещё один-два мегабайта под всё остальное. Таким образом, потребление памяти составит около 40 мегабайт.

Пример решения на Free Pascal

const

NMAX = 200; PMAX = 100;

type

Point = record

x, y, player, pathLength: smallint;

end;

var

field: array[1..NMAX] of String[NMAX];

queue: array[1..NMAX * NMAX * PMAX] of Point;

qLeft, qRight, nY, nX, player, needPlayers, x,y, dx, dy: longint;

playersCount: array[1..NMAX, 1..NMAX] of smallint;

visited: array[1..PMAX, 1..NMAX, 1..NMAX] of boolean;

p, pNext: Point;

begin

fillchar(visited, sizeof(visited), false);

fillchar(playersCount, sizeof(playersCount), 0);

readln(nY, nX, needPlayers);

for y := 1 to nY do

readln(field[y]);

player := 0; qRight := 0;

for y := 1 to nY do

for x := 1 to nX do

if field[y][x] = '*' then begin

inc(player);

p. x := x;

p. y := y;

p. player := player;

p. pathLength := 0;

inc(qRight);

queue[qRight] := p;

playersCount[y][x] := 1;

visited[player][y][x] := true;

end;

qLeft := 1;

while (qLeft <= qRight) do begin

p := queue[qLeft];

inc(qLeft);

for dy := -1 to 1 do

for dx := -1 to 1 do

if ((dy<>0)xor(dx<>0)) and (p. y+dy>=1)

and (p. x+dx>=1) and (p. y+dy<=nY)

and (p. x+dx<=nX) then

if (field[p. y+dy][p. x+dx] <> '#') and

(not visited[p. player][p. y+dy][p. x+dx]) then

begin

pNext. y := p. y + dy;

pNext. x := p. x + dx;

pNext. player := p. player;

pNext. pathLength := p. pathLength + 1;

inc(playersCount[pNext. y][pNext. x]);

if (playersCount[pNext. y][pNext. x]>=needPlayers)

then begin

writeln(pNext. pathLength);

exit;

end;

visited[pNext. player][pNext. y][pNext. x] := true;

inc(qRight);

queue[qRight] := pNext;

end;

end;

writeln(-1);

end.

Задача 5. Олимпиады (10 баллов)

Занесём информацию об олимпиадах в логические массивы t1, ta и tb, где:

·  t1[i] = true, если имеется однотуровая олимпиада, идущая в день i,

·  ta[i] = true, если имеется двухтуровая олимпиада без промежутка между турами, у которой второй тур идёт в день i,

·  tb[i] = true, если имеется двухтуровая олимпиада с промежутком между турами, у которой второй тур идёт в день i.

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

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

Теперь у нас осталась задача только с двухтуровыми олимпиадами. Воспользуемся методом динамического программирования. Создадим два массива ca и cb, где:

·  ca[i] − это максимальное количество двухтуровых олимпиад, в которых можно поучаствовать в дни 1..i,

·  cb[i] − это максимальное количество двухтуровых олимпиад, в которых можно поучаствовать в дни 1..i при условии, что в день (i1) мы в олимпиадах не участвуем

Осталось определиться, каким образом вычислять ca[i] и cb[i], зная их значения при всех меньших i. Для этого рассмотрим несколько случаев:

1. Можно в i-й день вообще ни в какой олимпиаде не участвовать. Тогда:

ca[i] := ca[i - 1];

cb[i] := ca[i - 2];

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

if ta[i] and (ca[i - 2] + 1 > ca[i]) then

ca[i] := ca[i - 2] + 1;

3. Можно в i-й день поучаствовать во втором дне олимпиады с промежуточным днём между турами (если такая, конечно, в этот день проводится). Для этого нужно, чтобы день (i−2) был свободен:

if tb[i] then begin

ca[i] := max(ca[i], ca[i - 3] + 1);

ca[i] := max(ca[i], cb[i - 1] + 1);

cb[i] := max(cb[i], ca[i - 3] + 1);

end;

Пример решения на Free Pascal

uses math;

const

NMAX = 1000; DMAX = 300;

var

n, i, t, d1, d2, ans: integer;

t1, ta, tb: array[0..DMAX + 2] of boolean;

ca, cb: array[0..DMAX + 2] of Integer;

begin

read(n);

for i := 0 to DMAX + 2 do begin

t1[i] := false; ta[i] := false; tb[i] := false;

ca[i] := 0; cb[i] := 0;

end;

for i := 1 to n do begin

read(t, d1);

if t = 1 then

t1[d1] := true

else begin

read(d2);

if d1 + 1 = d2 then

ta[d2] := true

else

tb[d2] := true

end;

end;

ans := 0;

for i := 1 to DMAX do

if t1[i] then begin

inc(ans);

ta[i] := false;

ta[i + 1] := false;

tb[i] := false;

tb[i + 2] := false;

end;

if ta[2] then ca[2] := 1;

for i := 3 to DMAX + 2 do begin

// если этот день пропустить

ca[i] := ca[i - 1];

cb[i] := ca[i - 2];

// если поучаствовать в олимпиаде без промежуточного дня

if ta[i] and (ca[i - 2] + 1 > ca[i]) then

ca[i] := ca[i - 2] + 1;

// если поучаствовать в олимпиаде со свободным днём

if tb[i] then begin

ca[i] := max(ca[i], ca[i - 3] + 1);

ca[i] := max(ca[i], cb[i - 1] + 1);

cb[i] := max(cb[i], ca[i - 3] + 1);

end;

end;

ans := ans + max(ca[DMAX + 2], cb[DMAX + 2]);

writeln(ans);

end.

Подзадачу 1 можно решить полным перебором подмножеств.

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

Дорешивание задач будет доступно по адресу

http://atpp. vstu. edu. ru/acm

(в разделе "Задачи с соревнований" - "Школьные олимпиады Вологодской области).