:: алгоритмы  и методы :: :: олимпиадные задачи :: :: связь :: :: о сайте ::
Путь: Олимпиадные задачи по программированию » Решения: Структуры данных
  Решения: Структуры данных



Решение задачи 1.

Пусть при записи этих N пар встретилось всего K различных символов, которые образуют множество X.

Идея решения задачи состоит в последовательном присвоении каждому символу s из Х номера, который определяет количество Р(s) элементов, предшествующих ему, с учетом свойства транзитивности (из (с,b) и (b,а) следует (с,а)). Это осуществляется следующим образом:

Первоначально предполагается, что каждому символу не предшествует ни один символ, т.е. Р(s)=0 для любого s.

При просмотре очередной пары (x,y) символу y присваивается большее из значений P(x)+1, P(y).

Очевидно, что при первом просмотре всех пар из входной последовательности определятся все упорядоченные цепочки длины 2, т.е. состоящие из 2 элементов. Поэтому номера некоторых элементов будут как минимум 1. При каждом из следующих просмотров входной строкивозможно несколько вариантов.

  1. Не произошло изменения ни одного из номеров символов. Если при этом номера символов различны и лежат в пределах от 0 до N-1, то эта нумерация определяет полный порядок. Иначе порядок неполный.
  2. Номер некоторого символа превысил N-1. Это произошло потому, что рост номеров неограничен, т.е. осуществляется циклически. Следовательно порядок противоречив.

Легко понять, что число просмотров не превысит N.

Вариант 2. Рассмотрим следующий метод:

Заведем массивы

A: array [1..N,0..N] of byte и Cnt: array[1..N] of byte;

сначала A[i,0]=0 и Cnt[i]=0 для любого i.

Пусть среди 2*N символов, образующих N пар, есть ровно K различных. Перенумеруем их от 1 до K. Будем считать, что пары составлены не из символов, а из соответствующих им номеров.

В i-ю строчку матрицы A будем заносить те элементы, которые являются вторыми элементами в парах с первым элементом i. В A[i,0] будет храниться текущее число этих элементов. Обработка пары (i,j) будет выглядеть следующим образом:

A[i,0]:=A[i,0]+1; {количество увеличилось на 1} A[i,A[i,0]]:=j; {вставляем j на первое свободное место}

В Сnt[i] будет храниться число пар, у которых элемент i является вторым в паре.

Если все символы без повторений, использованные для записи пар, можно выписать в цепочку в порядке предшествования, то у этой цепочки должен быть первый символ s, у которого нет предшествующего и которому соответствует Cnt[s]=0.

Может быть несколько ситуаций:

1. Такой элемент единственный - следовательно, это начало цепочки. Отбрасываем s из цепочки и убираем все пары с первым элементом s из множества пар, корректируя при этом массив Cnt:

for i:=1 to A[0,s] do Сnt[A[s,i]]:=Cnt[A[s,i]]-1;

после чего опять ищем элемент s, у которого нет предшествующего и которому соответствует Cnt[s]=0.

2. Таких элементов несколько, следовательно, между ними нельзя определить порядок предшествования - система неполна.

3. Таких элементов нет - следовательно, система противоречива.

[Назад]


Решение задачи 2.

Для решения этой задачи используем структуру данных 'список'. Каждый элемент списка указывает на следующий за ним элемент (другими словами - хранит информацию о расположении следующего элемента).

a). Заведем массив A из N ячеек - по числу людей в круге. В каждую из ячеек A[i] занесем номер стоящего следующим за i-ым человека. Первоначально A[i]=i+1, i=1,...,.N-1, A[N]=1. Начиная счет от текущего человека (человека с номером IndTek, с самого сначала IndTek=1) будем делать ход - отсчитывать M ячеек, двигаясь по кругу в поисках человека, который должен выйти из круга. С учетом определения массива A это будет выглядеть следующим образом:

{IndTek - это номер человека, с которого начинается счет} for i:=1 to M-1 do {Отсчитываем M человек, начиная с IndTek} begin IndPred:=IndTek; {в IndPred сохраняем номер текущего человека в круге} IndTek:=A[IndTek]; {и вычисляем номер следующего за ним} end;

После выполнения этого цикла в переменной IndTek будет номер человека, на котором остановился счет (т.е. номер человека, который покинул круг), а в переменной IndPred - номер предшествующего ему человека в круге.

Удалим человека с номером IndTek. Для этого мы просто изменим ссылку A[IndPred] у предшествующего ему человека так, чтобы он указывал не на IndTek, а на следующего за IndTek, т.е. на A[IndTek], и новый отсчет M-того человека начнем со следующего за удаленным:

A[IndPred]:=A[IndTek]; IndTek:=A[IndTek];{Новый номер начального человека}

Все вышеописанные операции мы будем повторять до тех пор, пока в круге не останется одного единственного человека, т.е. до тех пор, пока A[IndTek] не станет равным IndTek, что означает, что следующим за человеком с номером IndTek является он сам.

Полностью весь фрагмент программы будет выглядеть так:

{Инициализация массива A}
<P>for i:=1 to N-1 do
A[i]:=i+1;
A[N]:=1;
{IndTek - это номер человека, с которого начинается счет}
IndTek:=1;
while A[IndTek] <> IndTek do
begin
for i:=1 to M-1 do {Отсчитываем M человек, начиная с IndTek}
begin
IndPred:=IndTek; {в IndPred сохраняем номер текущего человека в круге}
IndTek:=A[IndTek]; {и вычисляем номер следующего за ним}
end;
A[IndPred]:=A[IndTek];
IndTek:=A[IndTek]; {Новый номер начального человека}
end;
writeln('Номер последнего оставшегося человека ',IndTek);

Решения пункта b).

Будем считать, что человек с номером N+i - это то же самое, что и человек с номером i.

Предположим, что как и в пункте a), что мы начали счет с первого человека, выбрасывали из круга M-того, и последний оставшийся в круге человек имеет номер K. Очевидно, что если бы мы начали счет со второго человека, то последний оставшийся в круге человек имел бы номер K+1, ..., если с j-го, то K+j-1.

Если номер оставшегося человека L, то из равенства L=K+j-1 определяем номер j первого человека (если j<=0, то заменим j на j+N, считая как и раньше, что человек с номером N+j - это то же самое, что и человек с номером j).

[Назад]


Решение задачи 3.

Будем моделировать сложение полоски, затем пронумеруем получившуюся колонку клеток числами от 1 до 2n, после чего распечатаем числа, приписанные первой, второй, ..., 2n-ой клетке исходной полоски.

Сначала создаем двусвязный список, состоящий из 2k элементов. Поле next будет указывать на элемент находящийся под данным, а поле last - на элемент находящийся над данным. Для верхнего элемента last=0, а для нижнего next=n+1, где n-общее число элементов. Вначале длина верхней полоски равняется n элементов, после первого сгибания их она станет n/2, после второго - n/4, и т.д. Пусть в данный момент длина верхней полоски есть cn элементов. Значит нам необходимо cn/2 правых элементов опустить под cn/2 левых. Для этого запустим цикл, который будет менять i от 1 до cn/2 и на каждом шаге помещать (cn-i+1)-ю колонку элементов под i-ю, при этом порядок элементов в (cn-i+1)-ой колонке меняется на противоположный. После каждого сгибания cn уменьшается вдвое. Так продолжаем до тех пор пока cn>1.

Программа (программы для задач 3 и 4 написаны В.Белым):

{$A-,B-,D-,E+,F-,I-,L-,N-,O-,R-,S-,V-}
{$M 65520,0,655360}
uses crt;
const
maxk = 13; {Максимальное значение для k}
type
input = record
last,next,new : word;
end;
var
k,i,j,n,cn,half : word;
m : array[1..1 shl maxk] of input;
Procedure concat(a,b : word);
var i,j,nj : word;
begin
i:=a;while m[i].next<>n+1 do i:=m[i].next;
j:=b; while m[j].next<>n+1 do j:=m[j].next;
while j<>0 do
begin
nj:=m[j].last; m[i].next:=j; m[j].last:=i; i:=j; j:=nj;
end;
m[i].next:=n+1;
end;
begin
Write('Enter k...');readln(k);
n:=1 shl k; {Определение длины полоски}
for i:=1 to n do{Начальные значения}
with m[i] do
begin
last:=0;
next:=n+1;
new:=0;
end;
cn:=n;
while cn>1 do {Сгибание полоски}
begin
half:=cn div 2;
for i:=1 to half do concat(i,cn-i+1);
cn:=half;
end;
j:=1;
for i:=1 to n do {Нумерация клеток}
begin
m[j].new:=i; j:=m[j].next;
end;
for i:=1 to n do write(m[i].new:5);
writeln;
end.

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

[Назад]


Решение задачи 4.

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

{$A-,B-,D-,E+,F-,G-,I+,L-,N-,O-,R-,S-,V-,X-}
{$M 16384,0,655360}
uses crt;
const
maxk = 6;
type
input = record
last1,last2,next1,next2,new : word;
end;
var
k,i,j,i1,i2,j1,j2,nj1,nj2,n,n1,cn,half : word;
m : array[1..1 shl maxk,1..1 shl maxk] of input;
Procedure concat(a,b,c,d : word);
var i1,i2,j1,j2,nj1,nj2 : word;
begin
i1:=a; i2:=b;
while (m[i1,i2].next1<>n+1) and (m[i1,i2].next2<>n+1) do
begin
i1:=m[i1,i2].next1; i2:=m[i1,i2].next2;
end;
j1:=c; j2:=d;
while (m[j1,j2].next1<>n+1) and (m[j1,j2].next2<>n+1) do
begin
j1:=m[j1,j2].next1; j2:=m[j1,j2].next2;
end;
while j1<>0 do
begin
nj2:=m[j1,j2].last2; nj1:=m[j1,j2].last1;
m[i1,i2].next1:=j1; m[i1,i2].next2:=j2;
m[j1,j2].last1:=i1; m[j1,j2].last2:=i2;
i1:=j1; i2:=j2; j1:=nj1; j2:=nj2;
end;
m[i1,i2].next1:=n+1; m[i1,i2].next2:=n+1;
end;
begin
Write('Введите k...');readln(k);
n:=1 shl k; {Определение числа клеток в одной строке или столбце}
n1:=n*n;  {Определение числа клеток в матрице}
for i:=1 to n do
for j:=1 to n do with m[i,j] do 
begin
last1:=0; next1:=n+1;
last2:=0; next2:=n+1;
new:=0;
end;
cn:=n;
while cn>1 do {сгибание матрицы}
begin
half:=cn div 2;
for i:=1 to half do {сгиб по вертикали}
for j:=1 to cn do concat(j,i,j,cn-i+1);
for i:=1 to half do {сгиб по горизонтали}
for j:=1 to half do concat(i,j,cn-i+1,j);
cn:=half;
end;
j1:=1;j2:=1;
for i:=1 to n1 do {Назначение клеткам новые номера}
begin
m[j1,j2].new:=i;
nj1:=m[j1,j2].next1; nj2:=m[j1,j2].next2;
j1:=nj1; j2:=nj2;
end;
for i:=1 to n do {Вывод результатов}
begin
for j:=1 to n do write(m[i,j].new:8);
writeln;
end;
end.

[Назад]


Решение задачи 5.

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

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

  1. Из очереди извлекается очередной элемент, который определяет некоторую позицию (x,y).
  2. Находятся клетки в пределах поля, которые достижимы из (x,y) одним ходом шахматного коня, и которые еще не помечены.
  3. Элементы, соответствующие найденным клеткам, помещаются в очередь, а клетки помечаются как посещенные.

При этом вначале все клетки должны быть непомечены, а метку желательно ставить такую, чтобы можно было восстановить путь между полями.

Для этого метку поля можно определить как позицию поля, из которой она была помечена.

[Назад]


Решение задачи 6.

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

1. Если очередная скобка - открывающая, то помещаем ее в стек.

2. Если очередная скобка - закрывающая, то анализируем скобку,

стоящую в вершине стека. Возможно несколько ситуаций:

а) открывающая скобка соответствует очередной закрывающей. В этом случае она выталкивается из стека, а процесс с новой входной скобкой.

б) открывающая скобка не соответствует очередной закрывающей или стек пуст. В этом случае невозможно получить правильное арифметическое выражение.

Когда все скобки входной строки обработаны, возможны 2 ситуации.

1. Стек пуст. В этом случае можно получить правильное арифметическое выражение.

2. Стек не пуст. В этом случае невозможно получить правильное арифметическое выражение.

[Назад]


Решение задачи 7.

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

[Назад]


Решение задачи 8.

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

Для этого для каждой остановки введем тройку чисел (О,М,За), где О - номер остановки, М - номер маршрута, За - величина задержки. При выборе очередного элемента из очереди возможны 2 ситуации.

  1. Продолжается движение по тому же маршруту. В этом случае в очередь заносятся тройки типа (О',М,За), где О' - номер остановки, соседней О, в маршруте М, а За=0.
  2. 2.Изменяется маршрут. В этом случае в очередь заносятся тройки типа (О,М',За), где М' - номер измененного маршрута, За=3 (задержка на пересадку).

При этом новые тройки порождаются только тройками с задержкой, равной 0. В случае тройки с ненулевой задержкой она вновь помещается в очередь с уменьшенным на 1 значением задержки.

[Назад]


Решение задачи 9.

Будем хранить в массиве A[1..N,1..2] координаты тех клеток, где король уже был. Возьмем N достаточно большим, N=500. A[i,1] - иксовая, A[i,2] - игрековая координата клетки. Пусть в переменной DLN хранится количество уже сделанных ходов (т.о. в массиве A после хода номер i будут заняты клетки с первой по i-ую).

Результаты очередного хода заносим в массив A на первое свободное место (т.е. на место с индексом DLN+1), увеличиваем количество сделанных ходов DLN:=DLN+1, и проверяем, совпадает ли хоть одна из позиций, на которых король уже был, с текущей позицией:

for i:=1 to DLN-1 do if (A[i,1]=A[DLN,1]) and (A[i,2]=A[DLN,2]) {проверка совпадения} then begin writeln('совпадение на ходах ',DLN,' и ',i); halt; {останов} end;

Давайте посмотрим, как можно вводить ходы короля. Он из клетки k может пойти на 8 соседних позиций.

1

2

3

Будем вводить направление перемещения короля

8

K

4

цифрой от 1 до 8.

7

6

5

Заведем массив XOD=array[1..8,1..2]=((-1,1),(0,1),(1,1),

(1,0),(1,-1),(0,-1),(-1,-1),(-1,0));

В массив заносятся смещения по x- и y-координатам, соответствующие направлениям от 1 до 8. Координаты короля после сделанного хода вычисляются по формулам

x:=x+XOD[i,1]; y:=y+XOD[i,2];

[Назад]


Решение задачи 10.

Монеты лежат на N+M позициях. Пронумеруем эти позиции по порядку по контуру от 1 до N+M.

Заведем массив A из N+M ячеек. Первоначально все ячейки нулевые. Начиная счет от первой ячейки, будем делать ход - отсчитывать S ячеек (считаем, что за N+M-ым элементом следует непосредственно 1-ый элемент массива) и заменять в этой ячейке число i на число 1-i (т.е. 0 на 1, а 1 на 0). После k-того хода остановимся.

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

Предположим, что после k ходов в массиве A стало p единиц т.е. p монет, по сравнению с начальным положением, будут перевернуты после k-ого хода.

В случае, если L>=N, то (L-N) монет, которые лежали гербами вниз, должны после k-ого хода быть перевернуты гербами вверх. (Если p<(L-N), то это, очевидно, невозможно сделать). Среди оставшихся p-(L-N) перевернутых монет должна быть половина гербами вверх и половина - вниз, чтобы при перевороте суммарное число монет гербами вверх и вниз не изменилось. Следовательно, число p-(L-N) должно быть четным, иначе условию задачи удовлетворить нельзя. Пусть p-(L-N) = 2v. Должно быть, очевидно, v<=N, v+(L-N)<=M.

Итак, в случае L>=N, если не выполняется хотя бы одно из неравенств

p-(L-N)=2v>=0,

v<=N,

v+(L-N)<=M,

то преобразование начальной конфигурации в конечнуюневозможно.

Иначе на (L-N) мест, помеченных в массиве A единицами, выставляем монеты гербами вниз. На оставшиеся 2v=p-(L-N) помеченных единицами позиций кладем v монет гербами вниз и v гербами вверх в произвольном порядке. На остальные позиции кладем оставшиеся монеты опять же в произвольном порядке, чтобы в общей сложности было N монет гербами вверх и M - гербами вниз.

Но мы еще не учли тот факт, что счет начинается с первой монеты гербом вверх:

а) Если в массиве A первый элемент нулевой, то в случае, если среди (N+M)-p монет есть хоть одна гербом вверх (что эквивалентно выполнению условия N-v>0), то ее и кладем в первую позицию. Если все (N+M)-p монеты - гербом вниз (т.е. N-v=0), то размещение монет невозможно.

б) Если же A[1]=1, то среди p монет должна быть по крайней мере одна, которую необходимо положить гербом вверх, иначе, опять же, размещение невозможно.

Случай N-L>0 разбирается аналогично.

[Назад]


Решение задачи 11.

Как и в задаче 10 заполняем сначала массив A нулями, перенумеровываем по порядку N+M позиций от 1 до N+M. Начиная с первой позиции (серая мышка) делаем ход - отсчитываем S нулевых позиций по порядку (считаем, что позиция, где сидела съеденная мышка, помечается единицей, несъеденная мышка - нулем; за N+M-ой позицией располагается первая) и выставляем в соответствующую позицию 1 мышка съедена. Далее отсчет начинаем со следующей за съеденной мыши. (Для более быстрого поиска S-той мышки среди оставшихся в круге можно использовать список, описанный в задаче 2).

Делаем P=(N+M)-(K+L) ходов.

По условию задачи в первой позиции сидит серая мышка. Есть A[1]=1 (первая мышь была съедена), то в оставшихся P-1 единичной позиции в произвольном порядке расставляем N-K-1 серых и M-L белых мышей. В оставшихся незанятыми позициях рассаживаем опять же в произвольном порядке оставшихся мышей.

Если A[1]=0, и K=0, то начальной расстановки не существует (все серые мыши съедены, а должна остаться еще одна в первой позиции); если же K<>0, то в единичных позициях рассаживаем N-K серых и M-L белых мышей, а в оставшихся позициях - в первую позицию серую мышь, а во все остальные - белых и серых в произвольном порядке.

[Назад]


Решение задачи 12.

Идея решения состоит в последовательном определении множества клеток, принадлежащих тому же куску, что и некоторая фиксированная клетка.

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

Для подсчета общего количества кусков необходимо подсчитать количество клеток, выбранных в качестве начальных. Новая начальная клетка выбирается из множества клеток, не помеченных на предыдущих итерациях. Для хранения вновь помеченных клеток можно использовать очередь или стек, а пометку клеток можно проводить в матрице размера N*M.

[Назад]


Решение задачи 13.

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

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

т.е. за первым элементом находится A[1]=2 элемент, ..., за 4-ым - A[4]=0 (т.к. 4-ый элемент списка последний).

Пусть у нас есть указатель на начальный элемент списка и на последний (BEG и FIN соответственно). В списке n элементов.

Рассмотрим процедуру удаления элемента из начала списка (это соответствует переносу карточки на стол):

BEG:=A[BEG]; {теперь новый первый элемент списка - второй элемент старого списка}

Рассмотрим операторы перестановки элемента из начала списка в конец (это соответствует перемещению карточки сверху стопки под низ ее):

A[FIN]:=BEG; {следующей за последним элементом - бывший первый}
FIN:=BEG; {меняем ссылку на последний элемент}
BEG:=A[BEG] {новый первый элемент}
A[FIN]:=0 {корректировка ссылки у последнего элемента}
Фрагмент программы будет выглядеть так:
for i:=1 to N-1 do A[i]:=i+1;
A[N]:=0; {установка ссылок в списке}
BEG:=1; FIN:=N;
COLOR:=1; {белый цвет = 1, черный = 0}
while A[BEG]<>0 do 
{пока первый элемент не является} {одновременно и последним}
begin
BEFORE:=BEG; {сохраняем индекс начала списка}
BEG:=A[BEG]; {удаляем первый элемент из списка}
A[BEFORE]:=COLOR; {раскрашиваем удаленный элемент} 
{в нужный цвет}
COLOR:=1-COLOR; {меняем цвет}
A[FIN]:=BEG;  {переставляем элемент из}
FIN:=BEG;  {начала списка в конец}
BEG:=A[BEG];
A[FIN]:=0
end;
A[BEG]:=COLOR;  {раскрашиваем последний элемент}
{списка}
for i:=1 to N do  {распечатка цветов}
if A[i]=0
then writeln('элемент',i,' - черный')
else writeln('элемент',i,' - белый');

[Назад]


Решение задачи 14.

Для реализации данного алгоритма нам понадобится структура данных "очередь". Очередь в программировании, как и очередь в магазине, имеет начало и конец. Если приходит новый элемент, то он становится в конец очереди, если необходимо взять элемент из очереди, то он берется из ее начала. Очередь будем представлять в виде массива. Пусть у нас есть индекс первого - BEG и последнего FIN элементов очереди (если очередь пуста, то BEG=FIN+1; сначала же BEG=1, FIN=0).

Очередь опишем так: var Queue=array[1..MaxQ] of element;

Тут MaxQ - максимальное число элементов в очереди, element какой-то тип данных. В задаче ниже в качестве element можно взять такой

type element = record x: byte; y: byte; end;

где element - это запись, состоящая из x-овой и y-овой координаты элемента матрицы A=array[0..M+1,0..N+1] of integer. Индексы матрицы принимают такое значение из-за того, что мы будем окаймлять матрицу нулевыми элементами (так будет проще проверять выливание за край фигуры).

С очередью можно проводить операции:

вставка в очередь InQueue,

удаление из очереди OutQueue.

Procedure InQueue (x : element);
begin
FIN:=FIN+1; {на первое свободное место}
Queue[FIN]:=x; {ставим элемент x}
end;
Procedure OutQueue (var x : element);
begin
x:=Queue[BEG]; {берем первый элемент}
BEG:=BEG+1; {и изменяем указатель}
{на следующий за ним}
end;

Находим максимальный элемент D в матрице A. Пока все элементы в матрице A не станут равны D, повторяем следующую последовательность действий:

а) Находим в матрице минимальный ненулевой элемент z (если их несколько, то берем любой из них), и заносим его в очередь (очередь сначала пустая). Если этот минимальный ненулевой элемент z=D, то СТОП.

Сначала считаем, что p=D - это минимальный граничный элемент области, заполненной элементами со значением z.

б) Для каждого еще не просмотренного элемента очереди (пусть в матрице этот элемент находится в позиции (i,j)) повторяем следующее:

Для каждого соседа (сверху, снизу, справа, слева) данного

элемента (i,j) проводим проверку-просмотр:

ЕСЛИ (сосед <> z) ТО P=min{P,сосед} ИНАЧЕ ЕСЛИ сосед еще не просмотрен ТО координата соседа - в очередь (в очереди появился еще один непросмотренный элемент)

т.е. мы ищем минимальный окаймляющий элемент области, заполненной элементами со значением z.

Фрагмент программы поиска:

var Delta = array [1..4,1..2] of
 integer = ((0,1),(1,0),(-1,0),(0,-1));
{Delta - возможные смещения соседних клеток от текущей клетки}
Current, neighbor : element;
z : integer;
....
{Будем обозначать то, что элемент в матрице уже просмотрен}
{умножением его на -1}
{минимальный ненулевой элемент матрицы имеет значение z}
while BEG<>FIN+1 do begin
OutQueue(Current);
for i:=1 to 4 do begin
neighbor.x:=Current.x+Delta[i,1], neighbor.y:=Current.y+Delta[i,2],
if A[neighbor.x,neighbor.y]=z
then InQueue(neighbor)
else p:=min(A[neighbor.x,neighbor.y],p); end;
end;

Если в очереди нет больше непросмотренных элементов, то, если p<z (случай, когда данная область имеет "слив" за границы матрицы) в матрице во все просмотренные элементы из очереди заносим значение D (делаем их равными накопленному значению, чтобы больше их не рассматривать).

Если же p>z, то высчитываем, сколько воды поместится в "бассейн" с глубиной дна z и с высотой ограждающего бордюра p:

Объем = (p-z)* количество просмотренных элементов в очереди.

Добавляем полученный объем к ранее найденному объему воды,

заполняющему матрицу до высоты x.

Заполняем в матрице элементы, соответствующие просмотренным элементам из очереди, значением p ("Доливаем" воды в "бассейн" до уровня p).

Переход на пункт а).

Суммарный полученный объем воды и является искомым.

Окаймление матрицы, упомянутое выше, может выглядеть следующим образом: матрица [1..N, 1..M] окаймляется нулями так: вводятся дополнительные нулевые 0-ая и (N+1)-ая строки и 0-ой и (M+1)-ый столбцы

var A: array[0..N+1,0..M+1] of byte;
{ввод и окаймление нулями}
for i:=1 to N do begin
A[i,0]:=0; A[i,M+1]:=0;
for j:=1 to M do read(A[i,j]);
end;
for j:=0 to M+1 do begin
A[0,j]:=0; A[N+1,j]:=0;
end;

[Назад]


Решение задачи 15.

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

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

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

[Назад]


Решение задачи 16.

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

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

[ var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-2056213-1']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })();