end;
{base - максимальная степень 10, не превосходящая n}
k:=n;
{инвариант: осталось напечатать k с тем же числом
знаков, что в base; base = 100..00}
while base <> 1 do begin
| write(k div base);
| k:= k mod base;
| base:= base div 10;
end;
{base=1; осталось напечатать однозначное число k}
write(k);
(Типичная ошибка при решении этой задачи: неправильно обрабаты-
ваются числа с нулями посередине. Приведенный инвариант допуска-
ет случай, когда k < base; в этом случае печатание k начинается
со старших нулей.)
1.1.27. То же самое, но надо напечатать десятичную запись в
обратном порядке. (Для n = 173 надо напечатать 371.)
Решение.
k:= n;
{инвариант: осталось напечатать k в обратном порядке}
while k <> 0 do begin
| write (k mod 10);
| k:= k div 10;
end;
1.1.28. Дано натуральное n. Подсчитать количество решений
неравенства x*x + y*y < n в натуральных (неотрицательных целых)
числах, не используя действий с вещественными числами.
Решение.
k := 0; s := 0;
{инвариант: s = количество решений неравенства
x*x + y*y < n c x < k}
while k*k < n do begin
| ...
| {t = число решений неравенства k*k + y*y < n
| (при данном k) }
| k := k + 1;
| s := s + t;
end;
{k*k >= n, поэтому s = количество всех решений
неравенства}
Здесь ... - пока еще не написанный кусок программы, который
будет таким:
l := 0; t := 0;
{инвариант: t = число решений
неравенства k*k + y*y < n c y < l }
while k*k + l*l < n do begin
| l := l + 1;
| t := t + 1;
end;
{k*k + l*l >= n, поэтому t = число
всех решений неравенства k*k + y*y < n}
1.1.29. Та же задача, но количество операций должно быть
порядка (n в степени 1/2). (В предыдущем решении, как можно
подсчитать, порядка n операций.)
Решение. Нас интересуют точки решетки (с целыми координата-
* ми) в первом квадранте, попадающие внутрь круга
* * * радиуса (n в степени 1/2). Интересующее нас
* * * * множество (назовем его X) состоит из объедине-
* * * * ния вертикальных столбцов убывающей высоты.
* * * * * Идея решения состоит в том, чтобы "двигаться
вдоль его границы", спускаясь по верхнему его краю, как по
лестнице. Координаты движущейся точки обозначим . Введем
еще одну переменную s и будем поддерживать истинность такого ус-
ловия:
находится сразу над k-ым столбцом;
s - число точек в предыдущих столбцах.
Формально:
l - минимальное среди тех l >= 0, для которых не принад-
лежит X;
s - число пар натуральных x, y, для которых x < k и при-
надлежит X.
Обозначим эти условия через (И).
k := 0; l := 0;
while "<0,l> принадлежит X" do begin
| l := l + 1;
end;
{k = 0, l - минимальное среди тех l >= 0,
для которых не принадлежит X }
s := 0;
{инвариант: И}
while not (l = 0) do begin
| s := s + l;
| {s - число точек в столбцах до k-го включительно}
| k := k + 1;
| {точка лежит вне X, но, возможно, ее надо сдвинуть
| вниз, чтобы восстановить И }
| while (l <> 0) and (" не принадлежит X") do begin
| | l := l - 1;
| end;
end;
{И, l = 0, поэтому k-ый столбец и все следующие пусты, а
s равно искомому числу}
Оценка числа действий очевидна: сначала мы движемся вверх не бо-
лее чем на (n в степени 1/2) шагов, а затем вниз и вправо - в
каждую сторону не более чем на (n в степени 1/2) шагов.
1.1.30. Даны натуральные числа n и k, n > 1. Напечатать k
десятичных знаков числа 1/n. (При наличии двух десятичных разло-
жений выбирается то из них, которое не содержит девятки в пери-
оде.) Программа должна использовать только целые переменные.
Решение. Сдвинув в десятичной записи числа 1/n запятую на k
мест вправо, получим число (10 в степени k)/n. Нам надо напеча-
тать его целую часть, т. е. разделить (10 в степени k) на n на-
цело. Стандартный способ требует использования больших по вели-
чине чисел, которые могут выйти за границы диапазона представи-
мых чисел. Поэтому мы сделаем иначе (следуя обычному методу "де-
ления уголком") и будем хранить "остаток" r:
l := 0; r := 1;
{инв.: напечатано l разрядов 1/n, осталось напечатать
k - l разрядов дроби r/n}
while l <> k do begin
| write ( (10 * r) div n);
| r := (10 * r) mod n;
| l := l + 1;
end;
1.1.31. Дано натуральное число n > 1. Определить длину пе-
риода десятичной записи дроби 1/n.
Решение. Период дроби равен периоду в последовательности
остатков (докажите это; в частности, надо доказать, что он не
может быть меньше). Кроме того, в этой последовательности все
периодически повторяющиеся все члены различны, а предпериод име-
ет длину не более n. Поэтому достаточно найти (n+1)-ый член пос-
ледовательности остатков и затем минимальное k, при котором
(n+1+k)-ый член совпадает с (n+1)-ым.
l := 0; r := 1;
{инвариант: r/n = результат отбрасывания l знаков в 1/n}
while l <> n+1 do begin
| r := (10 * r) mod n;
| l := l + 1;
end;
c := r;
{c = (n+1)-ый член последовательности остатков}
r := (10 * r) mod n;
k := 0;
{r = (n+k+1)-ый член последовательности остатков}
while r <> c do begin
| r := (10 * r) mod n;
| k := k + 1;
end;
1.1.32 (Э. Дейкстра). Функция f с натуральными аргументами
и значениями определена так: f(0) = 0, f(1) = 1, f (2n) = f(n),
f (2n+1) = f (n) + f (n+1). Составить программу вычисления f (n)
по заданному n, требующую порядка log n операций.
Решение.
k := n; a := 1; b := 0;
{инвариант: 0 <= k, f (n) = a * f(k) + b * f (k+1)}
while k <> 0 do begin
| if k mod 2 = 0 then begin
| | l := k div 2;
| | {k = 2l, f(k) = f(l), f (k+1) = f (2l+1) = f(l) + f(l+1),
| | f (n) = a*f(k) + b*f(k+1) = (a+b)*f(l) + b*f(l+1)}
| | a := a + b; k := l;
| end else begin
| | l := k div 2;
| | {k = 2l + 1, f(k) = f(l) + f(l+1),
| | f(k+1) = f(2l+2) = f(l+1),
| | f(n) = a*f(k) + b*f(k+1) = a*f(l) + (a+b)*f(l+1)}
| | b := a + b; k := l;
| end;
end;
{k = 0, f(n) = a * f(0) + b * f(1) = b, что и требовалось}
1.1.33. То же, если f(0) = 13, f(1) = 17, а f(2n) =
43 f(n) + 57 f(n+1), f(2n+1) = 91 f(n) + 179 f(n+1) при n>=1.
Указание. Хранить коэффициенты в выражении f(n) через три
соседних числа.
1.1.34. Даны натуральные числа а и b, причем b > 0. Найти
частное и остаток при делении а на b, оперируя лишь с целыми
числами и не используя операции div и mod, за исключением деле-
ния на 2 четных чисел; число шагов не должно превосходить
C1*log(a/b) + C2 для некоторых констант C1, C2.
Решение.
b1 := b;
while b1 <= a do begin
| b1 := b1 * 2;
end;
{b1 > a, b1 = b * (некоторая степень 2)}
q:=0; r:=a;
{инвариант: q, r - частное и остаток при делении a на b1,
b1 = b * (некоторая степень 2)}
while b1 <> b do begin
| b1 := b1 div 2 ; q := q * 2;
| { a = b1 * q + r, 0 <= r, r < 2 * b1}
| if r >= b1 then begin
| | r := r - b1;
| | q := q + 1;
| end;
end;
{q, r - частное и остаток при делении a на b}
1.2. Массивы.
В следующих задачах переменные x, y, z предполагаются опи-
санными как 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, причём x[1]
<= x[2] <= ... <= x[n]. Найти количество различных чисел среди
элементов этого массива.
Решение. (1 вариант)
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. (Сообщил А.Л.Брудно.) Прямоугольное поле m на n раз-
бито на mn квадратных клеток. Некоторые клетки покрашены в чер-
ный цвет. Известно, что все черные клетки могут быть разбиты на
несколько непересекающихся и не имеющих общих вершин черных пря-
моугольников. Считая, что цвета клеток даны в виде массива типа
array [1..m] of array [1..n] of boolean;
подсчитать число черных прямоугольников, о которых шла речь.
Число действий должно быть порядка m*n.
Решение. Число прямоугольников равно числу их левых верхних
углов. Является ли клетка верхним углом, можно узнать, посмотрев
на ее цвет, а также цвет верхнего и левого соседей. (Не за-
будьте, что их может не быть, если клетка с краю.)
1.2.7. Дан массив x: array [1..n] of integer. Найти коли-
чество различных чисел среди элементов этого массива. (Число
действий должно быть порядка n*n.)
1.2.8. Та же задача, если требуется, чтобы количество
действий было порядка n* log n. (Указание. Смотри главу о сорти-
ровке.)
1.2.9. Та же задача, если известно, что все элементы масси-
ва - числа от 1 до k и число действий должно быть порядка n+k.
1.2.10. Дан массив x [1]..x[n] целых чисел. Не используя
других массивов, переставить элементы массива в обратном поряд-
ке.
Решение. Числа x [i] и x [n+1-i] нужно поменять местами для
всех i, для которых i < n + 1 - i, т.е. 2*i < n + 1 <=> 2*i <= n
<=> i <= n div 2:
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. Не ис-
пользуя дополнительных массивов, переставить начало и конец.
(Число действий порядка m+n.)
Решение. (1 вариант). Перевернем (расположим в обратном по-
рядке) отдельно начало и конец массива, а затем перевернем весь
массив как единое целое.
(2 вариант, А.Г.Кушниренко). Рассматривая массив записанным
по кругу, видим, что требуемое действие - поворот круга. Как из-
вестно, поворот есть композиция двух осевых симметрий.