Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)
Demon's Souls |#9| Heart of surprises

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Различные авторы Весь текст 991.22 Kb

Программирование в теоремах и задачах

Предыдущая страница Следующая страница
1 2 3 4 5 6 7  8 9 10 11 12 13 14 ... 85

что позволяет заполнять таблицу значений функции R.

     2.7.2 (Счастливые билеты). (Задача предлагалась на Всесоюз-
ной олимпиаде по программированию 1989 года). Последовательность
из 2n цифр (каждая цифра от 0 до 9) называется счастливым  биле-
том, если сумма первых n цифр равна сумме последних n цифр. Най-
ти число счастливых последовательностей данной длины.

     Решение. (Сообщено одним из участников олимпиады; к сожале-
нию,  не могу указать фамилию, так как работы проверялись зашиф-
рованными.) Рассмотрим более общую задачу: найти число  последо-
вательностей,  где  разница  между суммой первых n цифр и суммой
последних n цифр равна k (k = -9n,..., 9n). Пусть T(n, k) - чис-
ло таких последовательностей.
     Разобьем  множество  таких  последовательностей на классы в
зависимости от разницы между первой и  последней  цифрами.  Если
эта разница равна t, то разница между суммами групп из оставших-
ся  n-1 цифр равна k-t. Учитывая, что пар цифр с разностью t бы-
вает 10 - (модуль t), получаем формулу
   T(n,k) = сумма по t от -9 до 9 чисел (10-|t|) * T(n-1,  k-t).
(Некоторые слагаемые могут отсутствовать, так как k-t может быть
слишком велико.)
      Глава 3. Обход дерева. Перебор с возвратами.

     3.1. Ферзи, не бьющие друг друга: обход дерева позиций

     В  предыдущей главе мы рассматривали несколько задач одного
и того же типа: "перечислить все элементы  некоторого  множества
A". Схема решения была такова: на множестве A вводился порядок и
описывалась  процедура  перехода  от произвольного элемента мно-
жества A к следующему за ним (в этом порядке).  Такую  схему  не
всегда  удается  реализовать  непосредственно, и в этой главе мы
рассмотрим другой полезный прием перечисления всех элементов не-
которого множества. Его называют "поиск  с  возвратами",  "метод
ветвей  и границ", "backtracking". На наш взгляд наиболее точное
название этого метода - обход дерева.

     3.1.1. Перечислить все способы расстановки n ферзей на шах-
матной доске n на n, при которых они не бьют друг друга.

     Решение. Очевидно, на каждой из n горизонталей должно  сто-
ять  по  ферзю.  Будем  называть k-позицией (для k = 0, 1,...,n)
произвольную расстановку k ферзей на k нижних горизонталях (фер-
зи могут бить друг друга). Нарисуем "дерево позиций": его корнем
будет единственная 0-позиция, а из каждой  k-позиции  выходит  n
стрелок  вверх в (k+1)-позиции. Эти n позиций отличаются положе-
нием ферзя на (k+1)-ой горизонтали. Будем считать, что  располо-
жение  их  на рисунке соответствует положению этого ферзя: левее
та позиция, в которой ферзь расположен левее.

                                        Дерево позиций для
                                           n = 2








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

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




                                         Дерево допустимых
                                         позиций для n = 3









     Разобьем задачу на две части: (1) обход произвольного дере-
ва и (2) реализацию дерева допустимых позиций.
     Сформулируем задачу обхода произвольного дерева. Будем счи-
тать, что у нас имеется Робот, который в каждый момент находится
в одной из вершин дерева (вершины изображены на рисунке  кружоч-
ками). Он умеет выполнять команды:

                              вверх_налево  (идти по самой левой
                                 из выходящих вверх стрелок)

                              вправо (перейти в соседнюю  справа
                                 вершину)

                              вниз (спуститься вниз на один уро-
                                 вень)

            вверх_налево
            вправо
            вниз

и проверки, соответствующие возможности выполнить каждую из  ко-
манд,   называемые  "есть_сверху",  "есть_справа",  "есть_снизу"
(последняя истинна всюду, кроме корня). Обратите  внимание,  что
команда "вправо" позволяет перейти лишь к "родному брату", но не
к "двоюродному".


                                    Так команда "вправо"
                                    НЕ действует!



     Будем считать, что у Робота есть команда "обработать" и что
его задача - обработать все  листья  (вершины,  из  которых  нет
стрелок вверх, то есть где условие "есть_сверху" ложно). Для на-
шей  шахматной  задачи  команде обработать будет соответствовать
проверка и печать позиции ферзей.

     Доказательство  правильности приводимой далее программы ис-
пользует такие определения. Пусть фиксировано положение Робота в
одной из вершин дерева. Тогда все листья дерева  разбиваются  на
три  категории: над Роботом, левее Робота и правее Робота. (Путь
из корня в лист может проходить через вершину с Роботом,  свора-
чивать  влево,  не доходя до нее и сворачивать вправо, не доходя
до нее.) Через (ОЛ) обозначим условие "обработаны все листья ле-
вее Робота", а через (ОЛН) - условие "обработаны все листья  ле-
вее и над Роботом".












Нам понадобится такая процедура:

  procedure вверх_до_упора_и_обработать
  | {дано: (ОЛ), надо: (ОЛН)}
  begin
  | {инвариант: ОЛ}
  | while есть_сверху do begin
  | | вверх_налево
  | end
  | {ОЛ, Робот в листе}
  | обработать;
  | {ОЛН}
  end;

Основной алгоритм:

  дано: Робот в корне, листья не обработаны
  надо: Робот в корне, листья обработаны

  {ОЛ}
  вверх_до_упора_и_обработать
  {инвариант: ОЛН}
  while есть_снизу do begin
  | if есть_справа then begin {ОЛН, есть справа}
  | | вправо;
  | | {ОЛ}
  | | вверх_до_упора_и_обработать;
  | end else begin
  | | {ОЛН, не есть_справа, есть_снизу}
  | | вниз;
  | end;
  end;
  {ОЛН, Робот в корне => все листья обработаны}

Осталось  воспользоваться  следующими  свойствами  команд Робота
(сверху записаны условия, в которых выполняется команда, снизу -
утверждения о результате ее выполнения):

   (1) {ОЛ, не есть_сверху}  (2) {ОЛ}
       обработать                вверх_налево
       {ОЛН}                     {ОЛ}

   (3) {есть_справа, ОЛН}    (4) {не есть_справа, ОЛН}
       вправо                    вниз
       {ОЛ}                      {ОЛН}

     3.1.2. Доказать, что приведенная программа завершает работу
(на любом конечном дереве).
     Решение. Процедура вверх_налево  завершает  работу  (высота
Робота  не может увеличиваться бесконечно). Если программа рабо-
тает бесконечно, то, поскольку листья не обрабатываются  повтор-
но, начиная с некоторого момента ни один лист не обрабатывается.
А  это  возможно,  только  если Робот все время спускается вниз.
Противоречие. (Об оценке числа действий см. далее.)

     3.1.3. Доказать правильность следующей программы обхода де-
рева:

  var state: (WL, WLU);
  state := WL;
  while есть_снизу or (state <> WLU) do begin
  | if (state = WL) and есть_сверху then begin
  | | вверх;
  | end else if (state = WL) and not есть_сверху then begin
  | | обработать; state := WLU;
  | end else if (state = WLU) and есть_справа then begin
  | |  вправо; state := WL;
  | end else begin {state = WLU, not есть_справа, есть_снизу}
  | |  вниз;
  | end;
  end;

     Решение. Инвариант цикла:
        state = WL  => ОЛ
        state = WLU => ОЛН
Доказательство завершения работы: переход из состояния ОЛ в  ОЛН
возможен  только  при  обработке вершины, поэтому если программа
работает бесконечно, то с некоторого момента значение  state  не
меняется, что невозможно.

    3.1.4.  Решить задачу об обходе дерева, если мы хотим, чтобы
обрабатывались все вершины (не только листья).

    Решение. Пусть x - некоторая вершина. Тогда любая вершина  y
относится к одной из четырех категорий. Рассмотрим путь из корня
в y. Он может:
    (а) быть частью пути из корня в x (y ниже x);
    (б) свернуть налево с пути в x (y левее x);
    (в) пройти через x (y над x);
    (г) свернуть направо с пути в x (y правее x);
В  частности,  сама вершина x относится к категории (в). Условия
теперь будут такими:
    (ОНЛ) обработаны все вершины ниже и левее;
    (ОНЛН) обработаны все вершины ниже, левее и над.
Вот как будет выглядеть программа:

  procedure вверх_до_упора_и_обработать
  | {дано: (ОНЛ), надо: (ОНЛН)}
  begin
  | {инвариант: ОНЛ}
  | while есть_сверху do begin
  | | обработать
  | | вверх_налево
  | end
  | {ОНЛ, Робот в листе}
  | обработать;
  | {ОНЛН}
  end;

Основной алгоритм:

  дано: Робот в корне, ничего не обработано
  надо: Робот в корне, все вершины обработаны

  {ОНЛ}
  вверх_до_упора_и_обработать
  {инвариант: ОНЛН}
  while есть_снизу do begin
  | if есть_справа then begin {ОНЛН, есть справа}
  | | вправо;
  | | {ОНЛ}
  | | вверх_до_упора_и_обработать;
  | end else begin
  | | {ОЛН, не есть_справа, есть_снизу}
  | | вниз;
  | end;
  end;
  {ОНЛН, Робот в корне => все вершины обработаны}

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

    Решение.  Под "обработано ниже и левее" будем понимать "ниже
обработано по разу, слева обработано полностью (листья по  разу,
останые по два)". Под "обработано ниже, левее и над" будем пони-
мать "ниже обработано по разу, левее и над - полностью".

Программа будет такой:

  procedure вверх_до_упора_и_обработать
  | {дано: (ОНЛ), надо: (ОНЛН)}
  begin
  | {инвариант: ОНЛ}
  | while есть_сверху do begin
  | | обработать
  | | вверх_налево
  | end
  | {ОНЛ, Робот в листе}
  | обработать;
  | {ОНЛН}
  end;

Основной алгоритм:

  дано: Робот в корне, ничего не обработано
  надо: Робот в корне, все вершины обработаны

  {ОНЛ}
  вверх_до_упора_и_обработать
  {инвариант: ОНЛН}
  while есть_снизу do begin
  | if есть_справа then begin {ОНЛН, есть справа}
  | | вправо;
  | | {ОНЛ}
  | | вверх_до_упора_и_обработать;
  | end else begin
  | | {ОЛН, не есть_справа, есть_снизу}
  | | вниз;
  | | обработать;
  | end;
  end;
  {ОНЛН, Робот в корне => все вершины обработаны полностью}

     3.1.6. Доказать, что число операций в этой программе по по-
рядку равно числу вершин дерева. (Как и в других программах, ко-
торые  отличаются от этой лишь пропуском некоторых команд "обра-
ботать".)
     Указание. Примерно каждое второе  действие  при  исполнении
этой программы - обработка вершины, а каждая вершина обрабатыва-
ется максимум дважды.

     Теперь реализуем операции с деревом позиций. Позицию  будем
представлять  с помощью переменной k: 0..n (число ферзей) и мас-
сива c: array [1..n] of 1..n (c [i] - координаты ферзя  на  i-ой
горизонтали; при i > k значение c [i] роли не играет). Предпола-
гается,  что  все позиции допустимы (если убрать верхнего ферзя,
остальные не бьют друг друга).

  program queens;
  | const n = ...;
  | var
  |   k: 0..n;
  |   c: array [1..n] of 1..n;
  |
  | procedure begin_work; {начать работу}
  | begin
  | | k := 0;
  | end;
  |
  | function danger: boolean; {верхний ферзь под боем}
  | | var b: boolean; i: integer;
  | begin
  | | if k <= 1 then begin
  | | | danger := false;
  | | end else begin
  | | | b := false; i := 1;
  | | | {b <=> верхний ферзь под боем ферзей с номерами < i}
  | | | while i <> k do begin
  | | | | b := b or (c[i]=c[k]) {вертикаль}
  | | | |     or (abs(c[[i]-c[k]))=abs(i-k)); {диагональ}
Предыдущая страница Следующая страница
1 2 3 4 5 6 7  8 9 10 11 12 13 14 ... 85
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (1)

Реклама