В следующих задачах переменные
предполагаются описанными как array[1..n] of integer
(где n -- некоторое натуральное число, большее 0), если
иное не оговорено явно.
1.2.1. Заполнить массив x нулями. (Это означает, что нужно
составить фрагмент программы, после выполнения которого все
значения x[1]..x[n] равнялись бы нулю, независимо от начального
значения переменной x.)
i := 0;
{инвариант: первые i значений x[1]..x[i] равны 0}
while i <> n do begin
| i := i + 1;
| {x[1]..x[i-1] = 0}
| x[i] := 0;
end;`
1.2.2. Подсчитать количество нулей в массиве x. (Составить
фрагмент программы, не меняющий значения x, после исполнения
которого значение некоторой целой переменной k равнялось бы
числу нулей среди компонент массива x.)
...
{инвариант: k = число нулей среди x[1]...x[i] }
... `
1.2.3. Не используя оператора присваивания для массивов, составить
фрагмент программы, эквивалентный оператору x:=y.
i := 0;
{инвариант: значение y не изменилось, x[l]=y[l] при l<=i}
while i <> n do begin
| i := i + 1;
| x[i] := y[i];
end;`
1.2.4. Найти максимум из x[1]..x[n].
i := 1; max := x[1];
{инвариант: max = максимум из x[1]..x[i]}
while i <> n do begin
| i := i + 1;
| {max = максимум из x[1]..x[i-1]}
| if x[i] > max then begin
| | max := x[i];
| end;
end;`
1.2.5. Дан массив x: array[1..n] of integer, причем
. Найти количество
различных чисел среди элементов этого массива.
i := 1; k := 1;
{инвариант: k - количество различных среди x[1]..x[i]}
while i <> n do begin
| i := i + 1;
| if x[i] <> x[i-1] then begin
| | k := k + 1;
| end;
end;
Вариант 2. Искомое число на 1 больше количества тех чисел i из 1..n-1, для которых x[i] не равно x[i+1].
k := 1; for i := 1 to n-1 do begin | if x[i]<> x[i+1] then begin | | k := k + 1; | end; end;`
1.2.6 (Сообщил А.Л. Брудно.) Прямоугольное поле
разбито на
квадратных клеток.
Некоторые клетки покрашены в черный цвет. Известно, что все
черные клетки могут быть разбиты на несколько непересекающихся и
не имеющих общих вершин черных прямоугольников. Считая, что
цвета клеток даны в виде массива типа
подсчитать число черных прямоугольников, о которых шла речь.
Число действий должно быть порядка
.
1.2.7. Дан массив x: array[1..n] of integer. Найти
количество различных чисел среди элементов этого массива. (Число
действий должно быть порядка
.)![]()
1.2.8. Та же задача, если требуется, чтобы количество действий
было порядка
.
1.2.9. Та же задача, если известно, что все элементы массива
-- числа от 1 до k и число действий должно быть порядка
.![]()
1.2.10. Дан массив x[1]..x[n] целых чисел. Не используя других
массивов, переставить элементы массива в обратном порядке.
for i := 1 to n div 2 do begin | ...обменять x [i] и x [n+1-i]; end;`
1.2.11 (из книги Д. Гриса). Дан массив целых чисел
x[1]..x[m+n], рассматриваемый как соединение двух его отрезков: начала
x[1]..x[m] длины m и конца x[m+1]..x[m+n] длины
n. Не используя дополнительных массивов, переставить начало
и конец. (Число действий порядка
.)
Вариант 1. Перевернем (расположим в обратном порядке) отдельно начало и конец массива, а затем перевернем весь массив как единое целое.
Вариант 2, А.Г. Кушниренко. Рассматривая массив записанным по кругу, видим, что требуемое действие -- поворот круга. Как известно, поворот есть композиция двух осевых симметрий.
Вариант 3. Рассмотрим более общую задачу -- обмен двух участков массива x[p+1]..x[q] и x[q+1]..x[r]. Предположим, что длина левого участка (назовем его A ) не больше длины правого (назовем его B ). Выделим в B начало той же длины, что и A , назовем его B1 , а остаток B2 . (Так что B=B1+B2 , если обозначать плюсом приписывание массивов друг к другу.) Нам надо из A+B1+B2 получить B1+B2+A . Меняя местами участки A и B1 -- они имеют одинаковую длину, и сделать это легко,-- получаем B1 + A + B2 , и осталось поменять местами A и B2 . Тем самым мы свели дело к перестановке двух отрезков меньшей длины. Итак, получаем такую схему программы:
p := 0; q := m; r := m + n;
{инвариант: осталось переставить x[p+1..q], x[q+1..r]}
while (p <> q) and (q <> r) do begin
| {оба участка непусты}
| if (q - p) <= (r - q) then begin
| | ..переставить x[p+1]..x[q] и x[q+1]..x[q+(q-p)]
| | pnew := q; qnew := q + (q - p);
| | p := pnew; q := qnew;
| end else begin
| | ..переставить x[q-(r-q)+1]..x[q] и x[q+1]..x[r]
| | qnew := q - (r - q); rnew := q;
| | q := qnew; r := rnew;
| end;
end;
Оценка времени работы: на очередном шаге оставшийся для
обработки участок становится короче на длину A ; число действий
при этом
также пропорционально длине A .
1.2.12. Коэффициенты многочлена лежат в массиве
(n -- натуральное число, степень многочлена).
Вычислить значение этого многочлена в точке x,
то есть
.
(Описываемый алгоритм называется схемой Горнера.)
k := 0; y := a[n];
{инвариант: 0 <= k <= n,
y= a[n]*(x в степени k)+...+a[n-1]*(x в степени k-1)+...+
+ a[n-k]*(x в степени 0)}
while k<>n do begin
| k := k + 1;
| y := y * x + a [n-k];
end;`
1.2.13. (Для знакомых с основами анализа. Сообщил
А.Г. Кушниренко) Дополнить алгоритм вычисления значения
многочлена в заданной точке по схеме Горнера вычислением
значения его производной в той же точке.
Общее утверждение о сложности вычисления производных таково:
1.2.14. Дана программа вычисления
значения некоторого многочлена
, содержащая
только команды присваивания. Их правые части -- выражения,
содержащие сложение, умножение, константы, переменные
и ранее встречавшиеся (в левой части) переменные.
Доказать, что существует программа того же типа, вычисляющая
все n производных
,причем общее число арифметических операций не более чем в C
раз превосходит число арифметических операций в исходной
программе. Константа C не зависит от n .
(В. Баур, Ф. Штрассен)
1.2.15. В массивах
хранятся коэффициенты двух многочленов
степеней k и l. Поместить в массив c: array[0..m] of
integer коэффициенты их произведения. (Числа
--
натуральные,
; элемент массива с индексом i содержит
коэффициент при степени i.)
for i:=0 to m do begin
| c[i]:=0;
end;
for i:=0 to k do begin
| for j:=0 to l do begin
| | c[i+j] := c[i+j] + a[i]*b[j];
| end;
end;`
1.2.16. Предложенный выше алгоритм перемножения многочленов требует
порядка n2 действий для перемножения двух многочленов степени
n . Придумать более эффективный (для больших n ) алгоритм,
которому достаточно порядка
действий.
1.2.17. Даны два возрастающих массива x: array[1..k] of
integer и
y: array[1..l] of integer. Найти количество общих
элементов в этих массивах, то есть количество тех целых t, для
которых
для некоторых i и j.
(Число действий порядка
.)
k1:=0; l1:=0; n:=0;
{инвариант: 0<=k1<=k; 0<=l1<=l;
искомый ответ = n + количество общих
элементов в x[k1+1]...x[k] и y[l1+1]...y[l]}
while (k1 <> k) and (l1 <> l) do begin
| if x[k1+1] < y[l1+1] then begin
| | k1 := k1 + 1;
| end else if x[k1+1] > y[l1+1] then begin
| | l1 := l1 + 1;
| end else begin {x[k1+1] = y[l1+1]}
| | k1 := k1 + 1;
| | l1 := l1 + 1;
| | n := n + 1;
| end;
end;
{k1 = k или l1 = l, поэтому одно из множеств, упомянутых в
инварианте, пусто, а n равно искомому ответу}`
Замечание. В третьей альтернативе достаточно было бы увеличивать одну из переменных k1, l1; вторая добавлена для симметрии.
1.2.18. Решить предыдущую задачу, если известно лишь, что
и
(возрастание заменено неубыванием).
...
end else begin {x[k1+1] = y[l1+1]}
| t := x [k1+1];
| while (k1<k) and (x[k1+1]=t) do begin
| | k1 := k1 + 1;
| end;
| while (l1<l) and (x[l1+1]=t) do begin
| | l1 := l1 + 1;
| end;
end;`
Замечание. Эта программа имеет дефект: при проверке условия
Но если мы не хотим полагаться на такое свойство используемой нами реализации паскаля (не предусмотренное его автором Н. Виртом), то можно поступить так. Введем дополнительную переменную b: boolean и напишем:
if k1 < k then b := (x[k1+1]=t) else b:=false;
{b = (k1<k) and (x[k1+1] = t)}
while b do begin
| k1:=k1+1;
| if k1 < k then b := (x[k1+1]=t) else b:=false;
end;
Можно также сделать иначе:
end else begin {x[k1+1] = y[l1+1]}
| if k1 + 1 = k then begin
| | k1 := k1 + 1;
| | n := n + 1;
| end else if x[k1+1] = x [k1+2] then begin
| | k1 := k1 + 1;
| end else begin
| | k1 := k1 + 1;
| | n := n + 1;
| end;
end;
Так будет короче, хотя менее симметрично.
Наконец, можно увеличить размер массива в его описании, включив в него фиктивные элементы.
1.2.19. Даны два неубывающих массива x: array[1..k] of
integer и y: array[1..l] of integer. Найти число
различных элементов среди
. (Число
действий порядка
.)![]()
1.2.20. Даны два массива
и
. << Соединить>> их в массив
(
; каждый элемент
должен входить в массив z столько раз, сколько раз он входит
в общей сложности в массивы x и y). Число действий
порядка m.
k1 := 0; l1 := 0;
{инвариант: ответ получится, если к z[1]..z[k1+l1] добавить
справа соединение массивов x[k1+1]..x[k] и y[l1+1]..y[l]}
while (k1 <> k) or (l1 <> l) do begin
| if k1 = k then begin
| | {l1 < l}
| | l1 := l1 + 1;
| | z[k1+l1] := y[l1];
| end else if l1 = l then begin
| | {k1 < k}
| | k1 := k1 + 1;
| | z[k1+l1] := x[k1];
| end else if x[k1+1] <= y[l1+1] then begin
| | k1 := k1 + 1;
| | z[k1+l1] := x[k1];
| end else if x[k1+1] >= y[l1+1] then begin
| | l1 := l1 + 1;
| | z[k1+l1] := y[l1];
| end else begin
| | { такого не бывает }
| end;
end;
{k1 = k, l1 = l, массивы соединены}
Этот процесс можно пояснить так. Пусть у нас есть две стопки
карточек, отсортированных по алфавиту. Мы соединяем их в одну
стопку, выбирая каждый раз ту из верхних карточек обеих стопок,
которая идет раньше в алфавитном порядке. Если в одной стопке
карточки кончились, берем их из другой стопки.
1.2.21. Даны два массива
и
. Найти их << пересечение>>,
то есть массив
, содержащий их общие
элементы, причем кратность каждого элемента в массиве z
равняется минимуму из его кратностей в массивах x и y.
Число действий порядка
.![]()
1.2.22. Даны два массива
и
и число q. Найти сумму вида
, наиболее
близкую к числу q. (Число действий порядка k+l, дополнительная
память -- фиксированное число целых переменных, сами массивы
менять не разрешается.)
1.2.23. (из книги Д. Гриса) Некоторое число содержится в каждом из
трех целочисленных неубывающих массивов
,
,
. Найти одно из таких чисел. Число
действий должно быть порядка
.
p1:=1; q1=1; r1:=1;
{инвариант: x[p1]..x[p], y[q1]..y[q], z[r1]..z[r]
содержат общий элемент}
while not ((x[p1]=y[q1]) and (y[q1]=z[r1])) do begin
| if x[p1]<y[q1] then begin
| | p1:=p1+1;
| end else if y[q1]<z[r1] then begin
| | q1:=q1+1;
| end else if z[r1]<x[p1] then begin
| | r1:=r1+1;
| end else begin
| | { так не бывает }
| end;
end;
{x[p1] = y[q1] = z[r1]}
writeln (x[p1]);`
1.2.24. Та же задача, только заранее не известно, существует ли
общий элемент в трех неубывающих массивах и требуется это
выяснить (и найти один из общих элементов, если они есть).![]()
1.2.25. Элементами массива a[1..n] являются неубывающие
массивы [1..m] целых чисел:
![]()
Известно, что существует число, входящее во все массивы a[i]
(существует такое х, что для всякого i из 1..n
найдется j из 1..m, для которого
). Найти одно из таких чисел х.
for k:=1 to n do begin
| b[k]:=1;
end;
eq := true;
for k := 2 to n do begin
| eq := eq and (a[1][b[1]] = a[k][b[k]]);
end;
{инвариант: оставшиеся части пересекаются, т.е. существует
такое х, что для всякого i из [1..n] найдется j из [1..m],
не меньшее b[i], для которого a[i][j] = х; eq <=> первые
элементы оставшихся частей равны}
while not eq do begin
| s := 1; k := 1;
| {a[s][b[s]] - минимальное среди a[1][b[1]]..a[k][b[k]]}
| while k <> n do begin
| | k := k + 1;
| | if a[k][b[k]] < a[s][b[s]] then begin
| | | s := k;
| | end;
| end;
| {a[s][b[s]] - минимальное среди a[1][b[1]]..a[n][b[n]]}
| b [s] := b [s] + 1;
| for k := 2 to n do begin
| | eq := eq and (a[1][b[1]] = a[k][b[k]]);
| end;
end;
writeln (a[1][b[1]]);`
1.2.26. Приведенное решение предыдущей задачи требует порядка
действий. Придумать способ с числом действий
порядка mn.
1.2.27. (Двоичный поиск) Дана последовательность
целых чисел и число a.
Выяснить, содержится ли a в этой последовательности, то есть
существует ли i из 1..n, для которого
.(Количество действий порядка
.)
l := 1; r := n+1;
{r > l, если a есть вообще, то есть и среди x[l]..x[r-1]}
while r - l <> 1 do begin
| m := l + (r-l) div 2 ;
| {l < m < r }
| if x[m] <= a then begin
| | l := m;
| end else begin {x[m] > a}
| | r := m;
| end;
end;
(Обратите внимание, что и в случае Каждый раз
уменьшается примерно вдвое, откуда
и вытекает требуемая оценка числа действий.![]()
Замечание:
![]()
1.2.28. (Из книги Д. Гриса) Дан массив
x: array[1..n] of
array[1..m] of integer, упорядоченный по
строкам и по столбцам:
![]()
и число a. Требуется выяснить, встречается ли a среди
x[i][j].
(допускаются пустые прямоугольники при
и
).
l:=n; k:=1;
{l>=0, k<=m+1, если a есть, то в описанном прямоугольнике}
while (l > 0) and (k < m+1) and (x[l][k] <> a) do begin
| if x[l][k] < a then begin
| | k := k + 1; {левый столбец не содержит a, удаляем его}
| end else begin {x[l][k] > a}
| | l := l - 1; {нижняя строка не содержит a, удаляем ее}
| end;
end;
{x[l][k] = a или прямоугольник пуст }
answer:= (l > 0) and (k < m+1) ;`
Замечание. Здесь та же ошибка: x[l][k] может оказаться неопределенным. (Ее исправление предоставляется читателю.)
1.2.29. (Московская олимпиада по программированию) Дан неубывающий
массив положительных целых чисел
. Найти наименьшее
целое положительное число, не представимое в виде суммы
нескольких элементов этого массива (каждый элемент массива может
быть использован не более одного раза). Число действий порядка
n.
k := 0; N := 0;
{инвариант: числа, представимые в виде суммы элементов
массива a[1]..a[k], заполняют отрезок 1..N}
while (k <> n) and (a[k+1] <= N+1) do begin
| N := N + a[k+1];
| k := k + 1;
end;
{(k = n) или (a[k+1] > N+1); в обоих случаях ответ N+1}
writeln (N+1);
(Снова тот же дефект: в условии цикла при ложном первом условии
второе не определено.)
1.2.30. (Для знакомых с основами алгебры) В целочисленном
массиве
хранится перестановка чисел
(каждое из чисел встречается по одному разу).
(а) Определить четность перестановки. (И в (а), и в (б) количество действий порядка n.)
(б) Не используя других массивов, заменить перестановку на
обратную (если до работы программы
, то после должно быть
).
1.2.31. Дан массив a[1..n] и число b. Переставить числа в
массиве таким образом, чтобы слева от некоторой границы стояли
числа, меньшие или равные b, а справа от границы -- большие или
равные b.
l:=0; r:=n;
{инвариант: a[1]..a[l]<=b; a[r+1]..a[n]>=b}
while l <> r do begin
| if a[l+1] <= b then begin
| | l:=l+1;
| end else if a[r] >=b then begin
| | r:=r-1;
| end else begin {a[l+1]>b; a[r]<b}
| | ..поменять a[l+1] и a[r]
| | l:=l+1; r:=r-1;
| end;
end;`
1.2.32. Та же задача, но требуется, чтобы сначала шли элементы,
меньшие b, затем равные b, а лишь затем большие b.
l:=0; m:=0; r:=n;
{инвариант: a[1..l]<b; a[l+1..m]=b; a[r+1]..a[n]>b}
while m <> r do begin
| if a[m+1]=b then begin
| | m:=m+1;
| end else if a[m+1]>b then begin
| | ..обменять a[m+1] и a[r]
| | r:=r-1;
| end else begin {a[m+1]<b}
| | ..обменять a[m+1] и a[l+1]
| | l:=l+1; m:=m+1;
end;`
1.2.33. (Вариант предыдущей задачи, названный в книге
Дейкстры задачей о голландском флаге.) В массиве стоят числа
0, 1 и 2. Переставить их в порядке возрастания, если
единственной разрешенной операцией (помимо чтения) над массивом
является перестановка двух элементов.![]()
1.2.34. Дан массив a[1..n] и число
. Для каждого
участка из m стоящих рядом членов (таких участков, очевидно,
) вычислить его сумму. Общее число действий
должно быть порядка n.
1.2.35. Дана квадратная таблица a[1..n][1..n] и число
. Для каждого квадрата
в этой
таблице вычислить сумму стоящих в нем чисел. Общее число
действий порядка
.
1.2.36. В массиве
встречаются по одному
разу все целые числа от 0 до n, кроме одного. Найти
пропущенное число за время порядка n и с конечной дополнительной
памятью.
Индуктивные функции