printРазбор задач

print1. Загадка

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

Лобовое решение выглядит так:
var s,t,k,n,i:integer;
begin
  read(s);
  for n:=4 to sqr(s-1) do
  begin
    k:=0;
    t:=0;
    for i:=n div 2 downto 1 do
      if n mod i=0 then
      begin
        inc(k);
        t:=t+i;
        if k=2 then
          break;
      end;
    if (k=2) and (t=s) then
      writeln(n);
  end;
end.
В качестве кандидатов нужно рассматривать числа, не превышающие `(S-1)^2`, так как в худшем случае составное число вида `x^2` будет иметь два наибольших делителя `x` и 1. Время работы этого алгоритма `O(S^4)`. Можно существенно ускорить вычисления, если учесть, что при поиске разложения на простые множители числа `N` можно рассмотреть только числа не превышающие `sqrt(N)`. Наибольшим делителем числа `N` будет `N/a`, где `a` – наименьший простой делитель, а вторым наибольшим делителем будет либо `N/b`, где `b` – второй по величине делитель `N` (также либо простое число, либо `a^2`), либо `a`, если `N` является произведением двух различных простых чисел, либо 1, если `N=a^2`.
var s,t,k,n,i,d:integer;
begin
  read(s);
  for n:=4 to sqr(s-1) do
  begin
    k:=0;
    t:=0;
    d:=1;
    for i:=2 to trunc(sqrt(n)) do
      if n mod i=0 then
      begin
        inc(k);
        t:=t + n div i;
        if i*i<n then d:=i;
        if k=2 then break;
      end;
    if k=1 then
    begin
      t:=t+d;
      inc(k);
    end;
    if (k=2) and (t=s) then
      writeln(n);
  end;
end.
Время работы алгоритма уменьшилось до `O(S^3)`, но остается достаточно большим. Существенно улучшить алгоритм, можно используя самый эффективный способ нахождения простых чисел – решето Эратосфена (вычеркивание из таблицы всех чисел, кратных простым):
const maxn=1000000;
var p:array [2..maxn] of boolean;
  i,j:integer;
begin
  for i:=2 to maxn do
    p[i]:=true;
  for i:=2 to maxn do
    if p[i] then
    begin
      j:=i*i;
      while j<=maxn do
      begin
        p[j]:=false;
        j:=j+i;
      end;
    end;
end.
Для каждого числа нужно найти два наименьших делителя, поэтому массив флагов в алгоритме решета заменяем на два массива целых чисел – наименьший простой делитель и следующий по величине делитель.
const maxn=1000000;
var 
  s,i,i2,j:integer;
  p1,p2:array[2..maxn] of integer;
begin
  read(s);
  for i:=2 to maxn do
  begin
    p1[i]:=1;
    p2[i]:=i;
  end;
  for i:=2 to maxn do
    if p1[i]=1 then { i – простое число }
    begin
      j:=2*i;
      while j<=maxn do
      begin
        if p1[j]=1 then p1[j]:=i
        else if p2[j]>i then p2[j]:=i;
        j:=j+i;
      end;
      { обработка случая, когда вторым делителем
        является квадрат наименьшего делителя }
      if i<=1000 then { чтобы избежать переполнения при умножении }
      begin
        i2:=i*i; 
        j:=i2;
        while j<=maxn do
        begin
          if p2[j]>i2 then p2[j]:=i2;
          j:=j+i2;
        end;
      end;
    end
    else { i – составное число }
      if (i div p1[i])+(i div p2[i])=s then
        writeln(i);
end.

print2. Карточки

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

Для каждой буквы считаем, сколько раз она встречается в каждом из слов и находим максимум из этих значений.
var
   n,i,j,m,k:integer;
   w:array [1..100] of string;
   c:char;
begin
  readln(n);
  for i:=1 to n do
    readln(w[i]);
  for c:='A' to 'Z' do
  begin
    m:=0;
    for i:=1 to n do
    begin
      k:=0;
      for j:=1 to length(w[i]) do
        if w[i][j]=c then
          inc(k);
      if k>m then
        m:=k;
    end;
    if m>0 then
      writeln(c,' ',m);
  end;
end.
Нахождение максимума можно совместить с вводом, используя массивы максимумов и количеств для каждой буквы.
var
   n,i,j:integer;
   w:string; 
   c:char;
   k1,ka:array['A'..'Z']of integer;
begin
  readln(n);
  for c:='A' to 'Z' do
    ka[c]:=0;
  for i:=1 to n do
  begin
    readln(w);
    for c:='A' to 'Z' do
      k1[c]:=0;
    for j:=1 to length(w) do
      inc(k1[w[j]]);
    for c:='A' to 'Z' do
      if k1[c]>ka[c] then
        ka[c]:=k1[c];
  end;
  for c:='A' to 'Z' do
  begin
    if ka[c]>0 then
      writeln(c,' ',ka[c]);
  end;
end.

print3. ИБП

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

Если использовать тип shortint (целое от 128 до 127) вместо обычного integer, то можно хранить значения всех измерений (100 x 100000 байт < 32Мб), поступивших от ИБП. Но фактически необходимы только `M` последних значений, которые удобно хранить в кольцевом буфере размера `M`. Также необходимо хранить количество значений меньше и равных `L` в буфере и их сумму. При поступлении очередного измерения от `j`-го ИБП изменяется только сумма и количество для `j`-го ИБП, предыдущее значение удаляется и вычитается из суммы, а новое записывается на его место в кольцевом буфере и прибавляется к сумме. Указатель буфера смещается на следующую позицию.
var N,L,M,K,i,j,p,b:integer;
  q:array[1..100,1..100] of integer;
  ki,li,si:array[1..100] of integer;
begin
  read(N,L,M,K);
  for i:=1 to N do
  begin
    ki[i]:=1;
    li[i]:=0;
    si[i]:=100*M;
    for j:=1 to M do
      q[i,j]:=100;
  end;
  for i:=1 to K do
  begin
    read(j,p);
    si[j]:=si[j]-q[j,ki[j]];
    if q[j,ki[j]]<=L then
      dec(li[j]);
    si[j]:=si[j]+p;
    if p<=L then
      inc(li[j]);
    q[j,ki[j]]:=p;
    inc(ki[j]);
    if ki[j]>M then
      ki[j]:=1;
    b:=0;
    for j:=1 to N do
    begin
      if li[j]=M then
      begin
        if b=0 then
          b:=j
        else if si[j]<si[b] then
          b:=j;
      end;
    end;
    writeln(b);
  end;
end.

print4. Ход конем

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

Для решения можно использовать волновой алгоритм, который заключается в следующем. Начальная позиция помечается значением 0. На `i`-ом шаге нужно найти пометку `i-1` и пометить все непомеченные клетки значением `i`.
var
  n,x,y,x1,y1,i,k,m:integer;
  u:array[1..100,1..100] of integer;
procedure xod(x2,y2:integer);
begin
  if (x2<1) or (x2>n) or (y2<1) or (y2>n) then
    exit;
  if u[x2,y2]>=0 then exit;
  u[x2,y2]:=i;
  inc(m);
end;
begin
  read(n,x,y,k);
  m:=1;
  for x1:=1 to n do
    for y1:=1 to n do
      u[x1,y1]:=-1;
  u[x,y]:=0;
  for i:=1 to k do
    for x:=1 to n do 
      for y:=1 to n do
        if u[x,y]=i-1 then
        begin
          xod(x+1,y+2);
          xod(x-1,y+2);
          xod(x+1,y-2);
          xod(x-1,y-2);
          xod(x+2,y+1);
          xod(x-2,y+1);
          xod(x+2,y-1);
          xod(x-2,y-1);
        end;
  writeln(m);
end.
Время работы этого алгоритма равно `O(N^2*K)`, что вполне укладывается в 1 секунду. Время можно уменьшить до `O(N^2)`, если использовать поиск в ширину, и новые отмеченные клетки заносить в очередь
var
  n,x,y,x1,y1,k1,k,q1,q2,m:integer;
  q:array[1..10000] of record x,y:integer; end;
  u:array[1..100,1..100] of integer;
procedure xod(x2,y2:integer);
begin
  if (x2<1) or (x2>n) or (y2<1) or (y2>n) then
    exit;
  if u[x2,y2]>=0 then exit;
  u[x2,y2]:=k1;
  inc(m);
  q[q2].x:=x2;
  q[q2].y:=y2;
  inc(q2);
end;
begin
  read(n,x,y,k);
  q[1].x:=x;
  q[1].y:=y;
  q1:=1;
  q2:=2;
  m:=1;
  for x1:=1 to n do
    for y1:=1 to n do
    u[x1,y1]:=-1;
  u[x,y]:=0;
  while q1<q2 do
  begin
    x:=q[q1].x;
    y:=q[q1].y;
    inc(q1);
    if u[x,y]<k then
    begin
      k1:=u[x,y]+1;
      xod(x+1,y+2);
      xod(x-1,y+2);
      xod(x+1,y-2);
      xod(x-1,y-2);
      xod(x+2,y+1);
      xod(x-2,y+1);
      xod(x+2,y-1);
      xod(x-2,y-1);
    end;
  end;
  writeln(m);
end.

print5. Горная дорога

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

Вычисление производится методом динамического программирования. Элемент матрицы `T[i,j]` содержит минимальную стоимость для проведения дороги в позицию после `i`-го участка горного рельефа на высоту `j`. `T[0,j]` вычисляется как `j^2*C_2/2`, так как попасть на высоту `j` можно только построив насыпь.

При вычислении `T[i,j]` возможны три случая.
3991.gif
A. Если высота `i`-го участка рельефа выше `j`, то попасть в эту точку можно только с помощью прокладки туннеля: `T[i,j]=T[i-1,j]+C_1`.

B. Если высота `i`-го участка рельефа равна `j`, то попасть в эту точку можно либо проведя дорогу по поверхности горы, либо построив наклонную насыпь с большей высоты: `T[i,j]=min(T[i-1,j],T[i-1,j+1]+C_2/2)`.

C. Если высота `i`-го участка рельефа меньше `j`, то попасть в эту точку можно либо построив наклонную насыпь с большей или меньшей высоты, либо построив v-образную дорогу (насыпать дорогу горизонтально нет смысла), либо построить мост, если возможно (далее на высоте j должна быть скала для правой опоры моста, и не более чем в 3 единицах должна быть скала для левой опоры моста). `T[i,j]=min(\ min(T[i-1,j]-C_2/4,T[i-1,j+1]+C_2/2,T[i-1,j-1]-C_2/2)\ +\ (j-h[i])*C_2,\ "вариант"\ с\ "мостом")`.

После заполнения матрицы нужно найти минимум среди значений `T[n,j]+\ j^2*C_2/2`.
var N,i,j,k,c1,c2,c3:integer;
  h:array[1..1000] of integer;
  t:array[0..1000,0..1000] of extended;
  r:extended;
procedure setmin(v:extended);
begin
  if v<t[i,j] then t[i,j]:=v;
end;
begin
  read(n,c1,c2,c3);
  for i:=1 to n do
    read(h[i]);
  for j:=0 to 1000 do
    t[0,j]:=j*j/2*c2;
  for i:=1 to n do
    for j:=0 to 1000 do
    begin
      if h[i]>j then
        t[i,j]:=t[i-1,j]+c1
      else if h[i]=j then
      begin
        t[i,j]:=t[i-1,j];
        if j<1000 then setmin(t[i-1,j+1]+c2/2);
      end
      else
      begin
        t[i,j]:=t[i-1,j]+(j-h[i])*c2-c2/4;
        if j<1000 then setmin(t[i-1,j+1]+(j-h[i])*c2+c2/2);
        setmin(t[i-1,j-1]+(j-h[i])*c2-c2/2);
        if (i<n) and (h[i+1]>=j) then
        begin
          for k:=1 to 3 do
            if (i>k) and (h[i-k]>=j) then
            begin
              setmin(t[i-k,j]+k*c3);
              break;
            end;
        end;
      end;
    end;
  r:=t[n,0];
  for j:=1 to 1000 do
    if r>t[n,j]+j*j/2*c2 then
      r:=t[n,j]+j*j/2*c2;
  writeln(r:1:2);
end.
loading