суббота, 30 марта 2013 г.

Разбор задач командного тура



A. Хлопушки (Морской бой – 2)

Автор задачи: Александр Торопов
Автор разбора: Александр Торопов
Для решения задачи переберем все клетки поля, и для каждой из них проверим, можно ли в нее поставить корабль. Рассмотрим клетку с координатами (i,j), находящуюся на пересечении i-ой строки и j-ого столбца. Поставить однопалубный корабль в эту клетку можно только в том случае, если она и ее соседи не заняты кораблями.
Соседями клетки являются две соседние клетки по вертикали (номер строки отличается на единицу) и две соседние клетки по горизонтали (номер столбца отличается на единицу). Таким образом, координаты этих клеток есть: (- 1,j), (+ 1,j), (i,j - 1) и (i,j + 1). Отметим, что соседей у клетки не всегда четыре — например, у угловой клетки всего два соседа. Приведем программную реализацию этого подхода.
Пример программы на Delphi:

readln(n, m);
for i := 1 to n do begin
  readln(s[i]);
end;

ans := 0;

for i := 1 to n do begin
  for j := 1 to m do begin
    if (s[i][j] = ’*’) then begin
          continue;
        end;
        t := 0;
        if ((i = 1) or (s[i - 1][j] = ’.’)) then begin
      inc(t);
        end;
        if ((i = n) or (s[i + 1][j] = ’.’)) then begin
          inc(t);
        end;
        if ((j = 1) or (s[i][j - 1] = ’.’)) then begin
      inc(t);
    end;
        if ((j = m) or (s[i][j + 1] = ’.’)) then begin
      inc(t);
    end;
        if (t = 4) then begin
          inc(ans);
        end;
  end;
end;

writeln(ans);

B. Конфеты (Сбор черники)

Автор задачи: Александр Торопов
Автор разбора: Сергей Поромов
В этой задаче необходимо найти три рядом расположенных куста таких, что сумма количества ягод на них максимальна. При этом необходимо учитывать то, что грядка круглая, и поэтому можно взять, например, последний и два первых куста. Заметим также, что число ягод невелико и для его хранения можно использовать 32-битный целочисленный тип данных (longint в языке Pascalint в языке Cint в языке Java).
Решение состоит в том, чтоб последовательно перебрать первый куст из тех, что мы берем, посчитать сумму количества ягод на нем и следующих двух. Из таких сумм необходимо найти максимум.
Этот алгоритм работает за линейное от время. Один из способов обрабатывать то, что грядка является круглой, — использовать массив длиной + 2 и в последние два элемента записать первые два.
Приведем программную реализацию этого алгоритма.
 
for i := 1 to n do
  read(a[i]);
a[n + 1] := a[1];
a[n + 2] := a[2];
ans := 0;
for i := 1 to n do begin
  if (a[i] + a[i + 1] + a[i + 2] > ans) then begin
    ans = a[i] + a[i + 1] + a[i + 2];
  end;
end;
write(ans);

С. СМС (Антипалиндром)

Автор задачи: Владимир Ульянцев
Автор разбора: Федор Царев
Заметим, что число подстрок строки длины равно 1 + 2 +  + = n * (n + 1)/2. Это объясняется тем, что строка длины содержит одну подстроку длины n, две подстроки длины - 1, …, n подстрок длины 1. Таким образом, время работы решения, основанного на переборе всех подстрок будет пропорционально как минимум n2. Такое решение не укладывается в ограничения по времени.
Для того, чтобы получить более быстрое решение, необходимо детальнее изучить свойства подстрок, не являющихся палиндромами. Во-первых, сама строка может не быть палиндромом. Тогда она и является ответом для задачи. Во-вторых, может оказаться так, что все подстроки строки являются палиндромами. Такое может быть только в том случае, если все символы равны между собой.
Докажем теперь, что если ни один из этих двух случаев не реализуется, то строка, получаемая из s отбрасыванием первого символа не является палиндромом. Итак, строка является палиндромом, но не все ее символы равны между собой. Обозначим как строку, получающуюся из отбрасыванием первого символа. Докажем с помощью метода «от противного», что не является палиндромом.
Для этого предположим, что s2s3…sn является палиндромом. Из этого следует, что ее первый и последний символ равны между собой, то есть s2 = sn. Из того, что равно палиндромом следует, что s1 =sn. Таким образом, получаем, что s1 = s2 = sn. Далее, из того, что является палиндромом так же следует, что sn-1 = s2. Значит, s1 = s2 = sn = sn-1. Из того, что является палиндромом следует, что s3 = sn-1. Значит, цепочка равенств расширяется далее: s1 = s2 = sn = sn-1 = s3. Продолжая это рассуждение, получаем, что все символы строки равны между собой, что противоречит предположению. Значит, доказано, что строка не является палиндромом.
Это рассуждение приводит нас к следующему решению. Возможны три случая:
  • все символы строки равны между собой — тогда ответ NO SOLUTION;
  • строка не является палиндромом — тогда ответом является сама строка s;
  • иначе ответом является строка t, получающаяся из отбрасыванием первого символа.
Приведем фрагмент программы, реализующий это решение.
 
read(s);
n := length(s);

allSame := true;
pali := false;

for i := 2 to n do begin
  if (s[i] <> s[i - 1]) then begin
    allSame := false;
  end;
end;

for i := 1 to n do begin
  if (s[i] <> s[n - i + 1]) then begin
    pali := true;
  end;
end;

if (allSame) then begin
  writeln(’NO SOLUTION’)
end else if (not pali) then begin
  for i := 1 to n - 1 do begin
    write(s[i]);
  end;
end else begin
  writeln(s);
end;

D. Задача (K-перестановки)

Автор задачи: Сергей Мельников
Автор разбора: Антон Феськов
Заметим, что перестановок из девяти чисел относительно немного, их число равно 9! = 362880. Поэтому можно перебрать все перестановки из чисел и проверить каждую их них, является ли она k-перестановкой.
Приведем фрагмента программы на языке Паскаль.
 
procedure go(pos : integer);
var
  i : integer;
begin
  if (pos = n + 1) then begin
    for i := 1 to n - 1 do begin
      if abs(p[i] - p[i + 1]) > k then begin
        exit;
      end;
    end;
    inc(ans);
    exit;
  end;
  for i := 1 to n do begin
    if (not w[i]) then begin
      w[i] := true;
      p[pos] := i;
      go(pos + 1);
      w[i] := false;
    end;
  end;
end;
Процедура go(pos) перебирает все возможные еще не использованные числа, которые можно поставить на позицию pos в перестановке p, если мы знаем все предыдущие числа. Для того, чтобы эффективно проверять, было использовано число или нет, поддерживается булев массив w, причем w[i] = true если уже использовано и false иначе. Если выполнено неравенство pos > n, то мы построили какую-то перестановку, осталось проверить, что она является k-перестановкой. Если это так, то увеличиваем ответ — переменную ans. 

E. Шарики (Индикатор)

Автор задачи: Федор Царев
Автор разбора: Федор Царев
Решение этой задачи, в целом, будет таким: в обоих случаях цифры будут приписываться к числу по одной, начиная со старших разрядов.
Для получения наименьшего числа необходимо обеспечить, чтобы в старших разрядах стояли как можно меньшие цифры (при этом необходимо следить за тем, что старшей цифрой не может быть ноль). Для получения наибольшего числа необходимо обеспечить, чтобы в старших разрядах стояли большие цифры.
Опишем более подробно алгоритм построения наименьшего числа. В каждый момент времени будут поддерживаться следующие величины: уже полученная часть числа num (некоторые его старшие разряды), число полосок, которые должны стать черными в оставшихся разрядах, и число цифр, которые необходимо дописать.
За один шаг к числу будет приписываться одна цифра. Для этого необходимо перебрать очередную цифру d. Обозначим число черных полосок в изображении цифры как black[d]. После приписывания цифры к существующему числу останется дописать к нему - 1 цифру, а число полосок, которые должны быть черными в оставшихся разрядах будет равно -black[d].
Тут необходимо проверить, хватит ли черных полосок на то, чтобы написать оставшиеся цифры. Например, если исходно = 2, = 8, а в качестве первой цифры мы пробуем поставить восьмерку, то на оставшуюся одну цифру остается одна полоска. Так как ни одной цифры, изображаемой с помощью одной полоски, не существует, то восьмерку в качестве старшей цифры поставить нельзя. С другой стороны, если = 2, = 10, а в качестве первой цифры выбрать единицу, то на оставшуюся цифру остается восемь полосок. Таких цифр также не существует, поэтому единица первой цифрой быть не может.
Поэтому возникает задача проверки того, может ли быть составлено число, содержащее цифр и kчерных полосок. Для того, чтобы решить эту подзадачу, заметим, что в изображении одной цифры могут участвовать две, три, четыре, пять, шесть или семь полосок. Наиболее важно тут то, что эти числа заполняют собой отрезок целых чисел от двух до семи. Значит, чисел из этого множества могут давать в сумме любое число от 2 * до 7 * n. Таким образом, для решения указанной подзадачи достаточно проверить двойное неравенство 2 * ≤ ≤ 7 * n.
Приведем фрагмент программы, реализующий описанный алгоритм.
 
min := ’’;
ck := k;
cn := n;
for i := 1 to n do begin
  st := 0;
  if (i = 1) then begin
    st := 1;
  end;
  for j := st to 9 do begin
    nk := ck - cnt[j];
    if (nk >= 2 * (cn - 1)) and (nk <= 7 * (cn - 1)) then begin
      min := min + chr(ord(’0’) + j);
      ck := nk;
      cn := cn - 1;
      break;
    end;
  end;
end;
Здесь переменная st обозначает цифру, с которой начинается перебор — для старшего разряда st = 1, для всех остальных — st = 0.
В случае построения максимального числа алгоритм такой же, только вместо перебора цифр по возрастанию (начиная от меньших) цифры перебираются по убыванию.

max := ’’;
ck := k;
cn := n;
for i := 1 to n do begin
  st := 0;
  if (i = 1) then begin
    st := 1;
  end;
  for j := 9 downto st do begin
    nk := ck - cnt[j];
    if (nk >= 2 * (cn - 1)) and (nk <= 7 * (cn - 1)) then begin
      max := max + chr(ord(’0’) + j);
      ck := nk;
      cn := cn - 1;
      break;
    end;
  end;
end;

2 комментария:

  1. Решения более изящные чем те, что сделал я. Спасибо за контест.

    ОтветитьУдалить
  2. Эти решения, скорее всего, были получены в спокойной "академической" обстановке. А ваши - в боевой. И они работали!

    ОтветитьУдалить