Подразделы

Другие разделы

Дата и время

19/12/2024 17:37:56

Авторизация

Имя:
Пароль:
Зарегистрироваться
Восстановить пароль
 

printРазбор задач отборочных командных соревнований школьников 2012

printРазбор задачи A. Расстояния в Плоском мире

Тема: реализация программы по заданному алгоритму
Сложность: простая

Алгоритм программы указан в условии задачи. Абсолютное значение (модуль) числа в языке Pascal можно вычислить с помощью функции abs.
var x1,y1,x2,y2:integer;
begin
  read(x1,y1,x2,y2);
  if y1=y2 then
    writeln(abs(x1-x2))
  else
    writeln(abs(x1)+abs(x2)+abs(y1-y2));
end.
Если функцию abs не использовать, то программа будет более сложной:
var x1,y1,x2,y2,r:integer;
begin
  read(x1,y1,x2,y2);
  if y1=y2 then
  begin
    r:=x1-x2;
    if r<0 then
      r:=-r;
  end
  else
  begin
    r:=y1-y2;
    if r<0 then
      r:=-r;
    if x1<0 then
      x1:=-x1;
    if x2<0 then
      x2:=-x2;
    r:=r+x1+x2;
  end;
  writeln(r);
end.

printРазбор задачи H. Новая столешница

Тема: геометрия
Сложность: простая

Для решения задачи проще всего использовать формулу Герона:
23100.png
Зная площадь столешницы `S`, можно найти её минимальный боковой размер – высоту: `h_c=2S/C`. И остается только сравнить высоту столешницы с диагональю прямоугольного проёма, которую можно вычислить по теореме Пифагора: `d^2=W^2\ +\ H^2`. Можно не вычислять корни, а сравнить квадраты высоты `h_c` и диагонали `d` (так как функция корня является монотонно возрастающей), и даже обойтись без сравнения дробных чисел, домножив обе части неравенства на знаменатель (так как знаменатель положительный).
var a,b,c,w,h:int64;
begin
  read(a,b,c);
  read(w,h);
  if (a+b+c)*(a+b-c)*(a-b+c)*(-a+b+c)<=4*c*c*(w*w+h*h) then
    writeln('YES')
  else
    writeln('NO');
end.

printРазбор задачи E. Определение победителя

Тема: обработка строк, редукция
Сложность: простая

Так как для каждой строки ввода нужно выполнить одинаковые преобразования в баллы и суммирование, удобно оформить это действие в виде подпрограммы:
procedure readncalc(var r:integer);
var i:integer;
    s:string;
begin
  r:=0;
  readln(s);
  for i:=1 to length(s) do
    if s[i]='J' then r:=r+3 { удар Jab - 3 балла }
    else if s[i]='K' then r:=r+2 { удар Kick - 2 балла }
    else r:=r+1; { остальные удары - 1 балл }
end;
Остальная часть программы выглядит после этого очень просто:
var r1,r2:integer;
begin
  readncalc(r1); { Ввод и расчет баллов для каждого участника }
  readncalc(r2);
  if r1>r2 then writeln(1) { Сравнение и вывод победителя }
  else if r1<r2 then writeln(2)
  else writeln(0);
end.

printРазбор задачи D. Соревнования

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

Перебираем все варианты количества человек в первой команде `A` от `N` до 1. Для количества гномов `B` есть два ограничения: 1) `B\ ≤\ A`; 2) `B*A\ ≤\ N`, т.е. `B\ ≤\ N/A`. Поэтому нужно рассматривать варианты количества гномов в диапазоне от `min(A,N/A)` до 1smilies/remark.gif. Количество троллей `C` для выбранных `A` и `B` определяется однозначно из формулы `A*B\ +\ A*C\ +\ B*C\ =\ N`: `C\ =\ (N\ -\ A*B)/(A+B)`, при этом 1) `C` должно быть целым числом и 2) `C\ ≤\ B`.
Оценка времени работы алгоритмы равна `O(N*sqrt(N))`.
var a,b,c,bn,n:longint;
begin
  read(n);
  for a:=n downto 1 do
  begin
    bn:=n div a;
    if bn>a then bn:=a;
    for b:=bn downto 1 do
    begin
      c:=(n-b*a) div (a+b);
      if (c<=b) and (a*b+b*c+a*c=n) then
        writeln(a,' ',b,' ',c);
    end;
  end;
end.

printРазбор задачи F. Лгуны и правдолюбы

Тема: задача на идею, логику
Сложность: ниже среднего

Пусть кто-то из героев назвал число `k`. Если этот герой является правдолюбом, то `k` героев (лгуны) должны назвать число, не совпадающее с `k`, а остальные `n-k` героев должны также сказать число `k`. Поэтому нужно подсчитать сколько раз названо каждое число `k` (для всех `k` от 0 до `n`). Если оно названо ровно `n-k` раз, то это один из вариантов количества лгунов в компании.
var sum:array[0..100] of integer;
    n,k,kv:integer;
begin
  read(n);
  { Считаем количество героев, сказавших некоторое число }
  for i:=1 to n do 
  begin
    read(k);
    inc(sum[k]);
  end;
  { Считаем количество вариантов }
  kv:=0; 
  for k:=0 to n do
    if sum[k]=n-k then
      inc(kv);
  { Выводим варианты }
  writeln(kv);
  if kv>0 then
  begin
    for k:=0 to n do
      if sum[k]=n-k then
        write(' ',k);
    writeln;
  end;
end.

printРазбор задачи B. Великий пожар Анк-Морпорка

Тема: поиск в ширину
Сложность: средняя

Для решения задачи необходимо использовать известный алгоритм поиска в ширину. Для лучшего понимания алгоритма рекомендуется рассмотреть разбор задач 741. Ход конем и 1509. Очистка озера.

Дополнительно можно воспользоваться следующими оптимизациями: 1) окружить поле клетками с '.'; 2) свести задачу к одному измерению (т.е. текущее состояние будет описываться одним целым числом).
Объявление необходимых структур данных:
var n,m:integer; { размеры карты }
  map:array [0..52*52] of char; { карта }
  q:array [0..50*50] of integer; { очередь } 
  q1,q2:integer; { индекс начала и конца очереди }
  step:array [0..52*52] of integer; { информация о времени возгорания, -1 означает, что дом не горел }
  maxkol,maxr,maxc, { наилучший результат и координаты дома для поджога }
  curstep,newstep, { текущее время и время возгорания следующего дома }
  qpos0,qpos1,qpos2, { начальные позиции в очереди для предыдущего, текущего и нового времени возгорания }
  i,j,p,pn:integer; {вспомогательные переменные }
Первая часть программы – ввод данных. Двумерная карта города размером `(n+2)\ times\ (m+2)` хранится в одномерном массиве последовательно по строкам. Ячейка с координатами `(i,j)` в одномерном массиве имеет индекс `i*(m+2)+j`. Строки карты с номерами 0 и `n+1` и столбцы с номерами 0 и `m+1` заполняются символами '.'.
  readln(n,m);
  for j:=0 to m+1 do
    map[j]:='.';
  for i:=1 to n do
  begin
    map[i*(m+2)]:='.';
    for j:=1 to m do
      read(map[i*(m+2)+j]);
    map[i*(m+2)+m+1]:='.';
    readln;
  end;
  for j:=0 to m+1 do
    map[(n+1)*(m+2)+j]:='.';
Далее перебираем все варианты, какой дом будет подожжен первым, и выполняем поиск в ширину. При изменении времени возгорания смещаем позиции указателей qpos0, qpos1, qpos2 в очереди и проверяем на увеличение текущего максимума одновременно горящих домов.
  maxkol:=0;
  for i:=1 to n do
    for j:=1 to m do
    begin
      p:=i*(m+2)+j; { выбираем клетку (i,j) }
      if map[p]='#' then { если там есть дом }
      begin
        for pn:=0 to (n+2)*(m+2)-1 do { очищаем массив времени возгорания }
          step[pn]:=-1;
        step[p]:=0; { дом в клетке (i,j) загорается в момент 0 }
        q[0]:=p; { помещаем его в очередь }
        q1:=0;q2:=1;
        curstep:=0;
        qpos0:=0;
        qpos1:=0;
        qpos2:=0;
        while q1<q2 do { очередь не пуста }
        begin
          p:=q[q1];
          inc(q1);
          newstep:=step[p]+1; { новое время }
          if newstep>curstep then { время изменилось }
          begin
            qpos0:=qpos1; {  сдвигаем указатели }
            qpos1:=qpos2;
            qpos2:=q2;
            curstep:=newstep;
            if qpos2-qpos0>maxkol then { найден новый максимум }
            begin
              maxkol:=qpos2-qpos0; { запоминаем }
              maxr:=i;
              maxc:=j;
            end;
          end;
          fire(p+1); { поджигаем все соседние дома }
          fire(p-1);
          fire(p+m+1);
          fire(p+m+2);
          fire(p+m+3);
          fire(p-m-1);
          fire(p-m-2);
          fire(p-m-3);
        end;
      end;
    end;
Подпрограмма fire выглядит следующим образом:
procedure fire(p:integer);
begin
  if (map[p]='#') and (step[p]=-1) then { в позиции p есть дом, который еще не горел }
  begin
    step[p]:=newstep; { отмечаем время возгорания }
    q[q2]:=p; { добавляем дом в очередь }
    inc(q2);
  end;
end;
В завершающей части программ выводится найденный максимум и координаты дома.
  writeln(maxkol);
  writeln(maxr,' ',maxc);
Другим способом ускорения работы своей программы является выключение проверок на выход за границы массива и переполнение, включенных по умолчанию при компиляции – см. опции компилятора на странице Помощь. Для этого в начале программы нужно дописать комментарий:
{$R-} {$Q-} {$S-}
Следует отметить, что на первоначальных этапах разработки эти проверки являются достаточно важными и дают дополнительную информацию для исправления ошибок в программе. Ошибка времени исполнения (RT) часто сигнализирует о неправильно выбранном диапазоне для цикла или неправильно выбранном типе данных (integer вместо longint или int64). При выключении проверок ошибка типа RT превращается в WA или PE, которые обычно сигнализируют о нерассмотренном случае или неверной формуле.
Рекомендуется выключать проверки только при получении TL, и такое выключение сможет помочь только в случае, если реализованный алгоритм решения задачи требует существенно меньше `10^9` операций. Иначе нужно изменять алгоритм.

printРазбор задачи I. Переправа

Тема: динамическое программирование
Сложность: средняя

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

Сначала определим необходимые структуры данных:
var n,m, { количество типов лодок и грузов }
    i,j, { вспомогательные переменные }
    k, { количество погруженных в лодку грузов }
    sumw:longint; { вес грузов, помещенных в очередную лодку }
    dt:array[0..100000] of record dif,tip,prd,kol:longint; end; 
       { таблица для динамического программирования: минимальный недогруз, 
         тип использованной лодки, начиная с какого груза в очереди, 
         количество лодок }
    w:array[1..100000] of integer; { вес грузов в очереди }
    g:array[1..50] of integer; { грузоподъемность лодок }
В первой части программы выполняется ввод и инициализация массивов:
  read(n,m);
  for i:=1 to n do
    read(g[i]);
  for i:=1 to m do
    read(w[i]);
  for i:=1 to m do
    dt[i].dif:=1000000000; { бесконечность }
  dt[0].dif:=0; { для 0 грузов недогруз равен 0 }
  dt[0].kol:=0;
Далее основной цикл по заполнению таблицы. Если известен минимальный недогруз для `i-1` груза, то, перебрав все типы лодок, можно получить минимальный недогруз для следующей партии грузов.
  for i:=1 to m do
  begin
    k:=0;
    sumw:=0;
    for j:=1 to n do
    begin
      while (i+k<=m) { есть грузы в очереди }
        and (sumw+w[i+k]<=g[j]) do { и их суммарный вес не превышает 
                                   грузоподъемности лодки j-го типа }
      begin
        sumw:=sumw+w[i+k];
        inc(k);
      end;
      { в лодку j-го типа загрузили k-1 груз, начиная с i-го, весом sumw }
      { последний загруженный в лодку груз имеет номер i+k-1 }
      { недогруз составляет g[j]-sumw }
      { минимальный недогруз для предыдущего груза в dt[i-1].dif }
      if dt[i-1].dif+g[j]-sumw<dt[i+k-1].dif then { если результат улучшился }
      begin
        dt[i+k-1].dif:=dt[i-1].dif+g[j]-sumw; { запоминаем }
        dt[i+k-1].tip:=j;
        dt[i+k-1].prd:=i-1;
        dt[i+k-1].kol:=dt[i-1].kol+1;
      end;
    end;
  end;
И наконец выводим результат.
  writeln(dt[m].kol,' ',dt[m].dif);
  print(m);
  writeln;
Для вывода списка типов лодок в обратном порядке здесь используется рекурсивная подпрограмма:
procedure print(i:integer);
begin
  if i=0 then exit;
  print(dt[i].prd); { сначала выводим предыдущие значения }
  write(' ',dt[i].tip); { затем тип последней использованной лодки }
end;

printРазбор задачи C. Проверка на сообразительность

Тема: нахождение наидлиннейшей возрастающей подпоследовательности, сортировка, бинарный поиск
Сложность: средняя

С первого взгляда кажется это известная задача на поиск наибольшей общей подпоследовательности (LCS), которая решается за время `O(N*M)`, но фраза "В каждой из последовательностей все числа попарно различны" упрощает задачу. Так как каждый элемент второй последовательно встречается не более одного раза в первой последовательности, то выписав последовательность номеров, под которым элемент из второй последовательности идет в первой последовательности (только для общих элементов последовательностей), мы сведем задачу к поиску наидлиннейшей возрастающей подпоследовательности, который выполняется за `O(N*log(N))`.
Подробное изложение этого алгоритма можно посмотреть на сайте e-maxx.ru
int d[MAXN];
k=0;
for (int i=0; i<n; i++) {
	int j = int (lower_bound (d, d+k, a[i]) - d);
        if(j==k)
           d[k++]=a[i];
        else	if (a[i] < d[j])
	  d[j] = a[i];
}
Для ускорения поиска элементов в первой последовательности необходимо использовать map или быструю сортировку (см. разбор задачи 1692. Шпаги) и бинарный поиск в получившейся упорядоченной последовательности (см. разбор задачи 1366. Дипломы).

printРазбор задачи G. Взаиморасчеты

Тема: задача на идею, поиск инварианта
Сложность: сложная

В основу задачи положена задача M1258 из журнала Квант №12 за 1990г. (автор задачи О. Ижболдин).
Разбор решения задачи был опубликован в №12 за 1991г.

Для сведения задачи к рассмотренной в журнале и для избавления от дробных чисел выполним замену `a'_i=(a_i-S)*N`, где `S` – среднее арифметическое чисел `a_i`.
Среднее станет равным 0 и результат операции обмена будет иметь вид `a_{(i-1)\ mod\ N}+a_i,\ -a_i,\ a_{(i+1)\ mod\ N}+a_i`. При рассмотрении частичных сумм `b_i=∑_{j=0..i}\ a_j` можно обнаружить, что операция сводится к обмену значений `b_{i-1}` и `b_i`.
Таким образом, получение конечного распределения возможно, если последовательность частичных сумм для конечного распределения состоит из перемешанных произвольным образом чисел `b_i+d`, где `d` – некоторая константа, совпадающая с одним из `b_i` (при обмене с 1 человеком происходит сдвиг `b_i` по кругу). Константу легко найти с помощью сортировки частичных сумм для начального и конечного распределения.
Затем нужно привести последовательность частичных сумм для начального распределения в соответствие с последовательностью частичных сумм для конечного распределения с помощью обменов (аналог сортировки пузырьком).
Время работы алгоритма `O(N^2)`.
loading