\documentclass[12pt,fleqn,a4paper]{book}
%
%
%
\usepackage[koi8-r]{inputenc}
\usepackage[russian,english]{babel}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{amsfonts}
\usepackage{amsthm}
\usepackage{url}
\usepackage{EZlist}
%
%
%
\pagestyle{headings}
\newtheorem{theorem}{Теорема}[chapter]
\newtheorem{lemma}{Лемма}[chapter]
\newtheorem{proposition}{Утверждение}[chapter]
\newtheorem{fact}{Факт}[chapter]
\theoremstyle{definition}
\newtheorem{problem}{Задача}[chapter]
\newtheorem{exercise}{Упражнение}[chapter]
\newtheorem{example}{Пример}[chapter]
\newtheorem{definition}{Определение}[chapter]
\newtheorem{remark}{Замечание}[chapter]
\newtheorem{algorithm}{Алгоритм}[chapter]
\newcommand{\marginnote}[1]{}
\def\gap{\medskip\centerline{\fbox{\Huge\bfseries{ПРОБЕЛ В КОНСПЕКТЕ.}}}\medskip}
\advance\headheight by 7pt
\def\headsep{15mm}
\newcommand{\lecture}[3]{%
\def\rightmark{\fbox{\parbox{125mm}{\lecturername: \coursetitle%
\hfil\phantom{.}}}}
\def\leftmark{\fbox{\parbox{125mm}{Лекция {#1}. {#2}%
\hfil\phantom{.}}}}
\renewcommand\chaptername{Лекция}\renewcommand\thechapter{#1}\chapter{{#2}\\{\small }}
\thispagestyle{headings}}
%
%
%
\newcommand\lecturername{Э. А. Гирш}
\newcommand\coursetitle{``Информатика'' (отд.математики, 1 курс)}
%
%
%
%
%
%
\begin{document}
\selectlanguage{russian}
\sloppy
%
%
%
\lecture{7}{Рисование планарного графа. Сложность рекурсивных алгоритмов.
Умножение матриц (над кольцом и булевых).
Нахождение пары ближайших точек на плоскости.}{2:15}
%
%
%
%
\section{Рисование планарного графа}\marginnote{35}
Планарным называется граф, который можно нарисовать на плоскости
без самопересечений. Будем рассматривать неориентированные графы.

Ниже мы представим алгоритм, который работает с 
двусвязным графом, то есть с графом, в котором не %таким, что в нем не 
существует вершины, удаление которой
%
ведет к потере связности.
%
(Заметим, что если граф не двусвязен, то в нем имеется некоторая вершина $v$,
удаление которой приводит к разбиению графа на $(V_1,E_1)$ и $(V_2,E_2)$;
тогда можно нарисовать подграфы, порожденные\footnote{Подграфом графа $(V,E)$,
\emph{порожденным} подмножеством вершин $V'\subseteq V$, называется
граф $(V',E')$, где $E'=\{\{x,y\}\in E\;|\;x\in V' \land y\in V'\}$.} 
$V_1\cup\{v\}$ и
$V_2\cup\{v\}$, по отдельности и соединить полученные рисунки;
чтобы отправить точку соединения на границу области, занимаемой графом,
рисуем граф на сфере, после чего <<раскрываем>> сферу, 
проделав дырку рядом с вершиной.)
Алгоритм будет рисовать граф на плоскости, если граф --- планарный.

\begin{definition}
Пусть дан граф $G=(V,E)$, из которого уже нарисовано
некоторое подмножество вершин $W\subseteq V$.
В этом разделе
окрестностью множества вершин $S\subseteq V\setminus W$
будем называть множество $\Gamma (S) = \{v\in W \,|\, \exists e \in E: 
e = \{v,s\}, s \in S \}$.
\end{definition}  

На каждом шаге работы нашего алгоритма плоскость будет разбита 
уже нарисованными частями графа на \emph{клетки}
(таким образом, каждая из клеток $K$ будет ограничена нарисованной
\emph{границей} $\partial K$ ---
некоторым циклом исходного графа).
Сам же граф будет уменьшаться и распадаться на компоненты связности
(мы будем выкидывать уже нарисованные вершины).
Очевидно, одна компонента может быть нарисована
только целиком в одной клетке.
Компонента \emph{совместна} с клеткой,
если все нарисованные вершины, 
бывшие в ее окрестности в исходном графе,
принадлежат границе этой клетки.

\begin{algorithm}\hfill
\begin{enumerate}
\item Взять какой-нибудь цикл, нарисовать его и выкинуть из графа
      (у нас получилось две клетки).
\item Если в оставшемся графе существует компонента, 
      совместная лишь с одной клеткой, 
      то взять
      путь в этой компоненте, соединяющий (вместе с двумя соответствующими
      ребрами исходного графа) две вершины границы этой клетки, 
      и нарисовать его в клетке (удалив путь из компоненты
      и разбив компоненту на несколько, если она развалилась).
\item Если каждая компонента согласована с несколькими клетками, то взять 
      любую компоненту и вставить путь в клетку 
      (удалив путь и\;т.\,д.) аналогично шагу~2.
\item Если еще не весь граф нарисован, вернуться к шагу~2.
\end{enumerate}\hfill$\qed$
\end{algorithm}

Заметим, что шаги 1 и 2 алгоритма являются вынужденными.
Поэтому его корректность вытекает из следующей леммы.
\begin{lemma}
Пусть в какой-то момент что-то уже нарисовано и алгоритм находится в 
шаге 3. Компонента $C$ согласована с клетками $K_1$ и $K_2$. Если $C$ 
можно вложить в $K_1$ (и успешно дорисовать граф до конца), 
то ее можно вложить и в $K_2$ (и успешно дорисовать).
\end{lemma}
\begin{proof}
Рассмотрим правильный рисунок (всего графа), в котором $C$ вложена в $K_1$.
Построим правильный рисунок (всего графа), в котором $C$ вложена в $K_2$.
В дальнейшем под клетками и компонентами понимаются те клетки и компоненты,
которые имелись в рассматриваемый момент времени.

Поменяем местами все компоненты, согласованные и с $K_1$, и с $K_2$
(назовем такие компоненты \emph{активными}):
те, что в исходном рисунке были нарисованы внутри $K_1$,
отправим в $K_2$, и наоборот.
Покажем, что их по-прежнему можно нарисовать без самопересечений.

Действительно, две активные компоненты всегда можно <<развести>>
на плоскости: ведь прежде они были разведены. Так что конфликт
может возникнуть только между активной и неактивной компонентами.
Перебором случаев проверим, что таких конфликтов также не должно возникнуть:
разобьем границы клеток $K_1$ и $K_2$ на участки, принадлежащие только $K_1$,
только $K_2$, либо им вместе; разберем случаи, когда конфликтующий путь
начинается на одном из участков, а заканчивается на другом.

Если окрестность неактивной компоненты целиком содержится в пути
(в нарисованной части графа), внутренние (не первая и не последняя)
вершины которого содержатся в $\partial K_i\setminus\partial K_{3-i}$,
то конфликта возникнуть не может (эти внутренние вершины
не могут входить в окрестность активной компоненты --- ведь
она согласована с обеими клетками!).
Если же окрестность не содержится в таком пути,
легко видеть (нарисуйте!), 
что неактивная компонента в рассматриваемый момент времени
была согласована только с одной клеткой, а это противоречит условию шага~3.
\end{proof}

\begin{exercise}
Важное упражнение на понимание: найдите, где используется двусвязность графа.
\qed
\end{exercise}

\begin{remark}
Существует алгоритм, позволяющий нарисовать планарный граф отрезками прямых
за линейное число операций.
\end{remark}

%
\section{Сложность рекурсивных алгоритмов}\marginnote{15}
Предположим, что алгоритм действует по схеме <<разделяй и властвуй>>,
т.е. сводит задачу к нескольким таким же задачам меньшего размера
и решает их. Тогда время его работы можно оценить при помощи
следующей теоремы (аналогично можно оценить и занимаемую память).

\begin{theorem}\label{th:recursion}
Пусть функция $T$ задана соотношениями
\[T(1)=1,\] 
\[T(n)\le aT(\lceil n/c\rceil)+bn^d\mbox{ при }$n>1$,\]
где $a,b,c,d\ge0$ --- константы.
Тогда\\[-7pt]
\begin{itemize} 
\item если $a<c^d$, то $T(n)=O(n^d)$;
\item если $a=c^d$, то $T(n)=O(n^d\log n)$;
\item если $a>c^d$, то $T(n)=O(n^{\log_c a})$.
\end{itemize}
\end{theorem}
\begin{remark}
При использовании этой леммы $n$ может быть любым параметром задачи,
а не только размером входа.
\end{remark}
\begin{proof}
Оценим $T(n)$ для $n$ вида $c^k$; 
результат для других $n$ будет простым следствием.

Раскрыв рекуррентное соотношение, получим
\[
T(n)\le
bn^d+aT(n/c)\le
bn^d+ab(n/c)^d+T(n/c^2)\le\ldots
\le bn^d\sum_{i=0}^k \left(\frac{a}{c^d}\right)^i.
\]

В случае $a<c^d$ эта $\sum_{i=0}^k$ ограничена $\sum_{i=0}^{+\infty}$,
а та, в свою очередь, константой. В случае $a=c^d$ имеем сумму из
$k=\log_c n$ единиц.
Если же $a>c^d$, вычислим сумму как сумму геометрической прогрессии.

Наконец, для произвольного $n$
\[
T(n)\le T_*(c^{\lceil \log_c n\rceil}) = O( T_*(n) ),
\]
где $T_*$ --- наша оценка (с конкретной константой вместо $O(\ldots)$).
\end{proof}

%
\section{Умножение матриц}\marginnote{20}
Задача: вычислить произведение $\mathbf{C}$ 
матриц $\mathbf{A}$ и $\mathbf{B}$ размера $n\times n$ над произвольным кольцом,
используя лишь операции кольца. Будем подсчитывать количество
этих операций. Как обычно, $n$ можно
считать степенью двойки.

\paragraph{Очевидный способ.} 
Поделим эти матрицы на четыре части, пополам по вертикали и горизонтали:
например,
$        \bf{A} = \begin{pmatrix}
                                \bf A_{11} & \bf A_{12} \\
                                \bf A_{21} & \bf A_{22}
                \end{pmatrix}
$.
Каждая из матриц разбиения будет иметь размерность 
$\frac{n}{2}\times\frac{n}{2}$.   
Сведем перемножение матриц размера $n\times n$ 
к перемножению матриц размера $\frac{n}{2}\times\frac{n}{2}$:
\begin{eqnarray*}
        {\bf C}_{11}&=&{\bf A}_{11}{\bf B}_{11} + {\bf A}_{12}{\bf B}_{21},\\ 
        {\bf C}_{12}&=&{\bf A}_{11}{\bf B}_{12} + {\bf A}_{12}{\bf B}_{22},\\
        {\bf C}_{21}&=&{\bf A}_{21}{\bf B}_{11} + {\bf A}_{22}{\bf B}_{21},\\
        {\bf C}_{22}&=&{\bf A}_{21}{\bf B}_{12} + {\bf A}_{22}{\bf B}_{22}.
\end{eqnarray*}   
Далее каждую из матриц ${\bf A}_{ij}$, ${\bf B}_{ij}$ 
опять поделим на четыре равные части,
и так далее, пока не сведем перемножение матриц к операциям 
перемножения элементов кольца. 

Подсчитаем количество $T(n)$ операций с элементами матриц,
выполняемых таким алгоритмом:
\[
 T(n) = 8 T\left(\frac{n}{2}\right) + cn^2, 
 \quad \text{где $c$ --- некоторая константа}.   
\]
По теореме~\ref{th:recursion}, $T(n)=O(n^3)$.

\paragraph{Алгоритм Штрассена.}
Опять рассмотрим такое же разбиение матриц и введем новые матрицы
\begin{eqnarray*}
{\bf M}_{1}&=&({\bf A}_{12} - {\bf A}_{22})({\bf B}_{21} + {\bf B}_{22}),\\
{\bf M}_{2}&=&({\bf A}_{11} + {\bf A}_{22})({\bf B}_{11} + {\bf B}_{22}),\\
{\bf M}_{3}&=&({\bf A}_{11} - {\bf A}_{21})({\bf B}_{11} + {\bf B}_{12}),\\
{\bf M}_{4}&=&({\bf A}_{11} + {\bf A}_{12}){\bf B}_{22},\\
{\bf M}_{5}&=&{\bf A}_{11}({\bf B}_{12} - {\bf B}_{22}),\\
{\bf M}_{6}&=&{\bf A}_{22}({\bf B}_{21} - {\bf B}_{11}),\\
{\bf M}_{7}&=&({\bf A}_{21} + {\bf A}_{22}){\bf B}_{11}.
\end{eqnarray*}     

Тогда ${\bf C}_{ij}$ можно выразить через ${\bf M}_{kl}$:
\begin{eqnarray*}
{\bf C}_{11}&=&{\bf M}_1+{\bf M}_2-{\bf M}_4+{\bf M}_6,\\
{\bf C}_{12}&=&{\bf M}_4+{\bf M}_5,\\
{\bf C}_{21}&=&{\bf M}_6+{\bf M}_7,\\
{\bf C}_{22}&=&{\bf M}_2-{\bf M}_3+{\bf M}_5-{\bf M}_7.
\end{eqnarray*}

Подсчитаем количество $T(n)$ операций с элементами матриц,
выполняемых таким алгоритмом:
\[
 T(n) = 7 T\left(\frac{n}{2}\right) + cn^2, 
 \quad \text{где $c$ --- некоторая константа}.   
\]
По теореме \ref{th:recursion}, 
$T(n)=O(n^{\log_27})$. Поскольку $\log_27\approx 2.80735$, 
этот алгоритм лучше предыдущего 
и лучше тривиального алгоритма (через вычисление каждого элемента 
матрицы $C$ по определению произведения матриц). 

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

\begin{exercise}
Где мы воспользовались принадлежностью \emph{кольцу} элементов матриц?
\end{exercise}

\begin{remark}
К умножению можно свести и обращение матриц (конечно, невырожденных 
и, к тому же, над полем). Для этого понадобится разложить матрицу в произведение
матриц специального вида (нижнетреугольную, верхнетреугольную
и матрицу перестановки). Если кому-то понадобится реализовать этот алгоритм,
можно прочесть в книге
Ахо, Хопкрофта и Ульмана или Кормена, Лейзерсона и Ривеста.
\end{remark}

\section{Умножение булевых матриц}\marginnote{10}
Произведение (конъюнкция) булевых матриц (их
элементами могут быть $T$ (истина) и $F$ (ложь)) определяется точно так же,
как и произведение обычных матриц, но в качестве умножения элементов
выступает конъюнкция $\land$, а в качестве сложения --- дизъюнкция $\vee$.
Мы не можем использовать наш быстрый алгоритм для перемножения булевых матриц, 
так как $T$ и $F$ 
с операциями $\lor$ и $\land$ не образуют кольца.

\begin{example}
Пример перемножения булевых матриц:
%
%

$$
        \begin{pmatrix} T & F \\
                        T & F
        \end{pmatrix}   \wedge
        \begin{pmatrix}
                        F & T \\
                        T & T
        \end{pmatrix} =
        \begin{pmatrix}
                        F & T \\
                        F & T
        \end{pmatrix}.
$$
\end{example}

\begin{theorem}
Умножение булевых матриц можно выполнить за $O(n^{\log7})$ арифметических
операций по модулю $n+1$.
\end{theorem}
\begin{proof}
Чтобы воспользоваться нашим быстрым алгоритмом, будем вместо
булевых операций $\lor$ и $\land$ использовать операции
сложения и умножения в кольце $\mathbb{Z}_{n+1}$, где $n$ -- размер матрицы.
Легко показать, что элемент произведения, вычисленного таким образом,
отличен от нуля тогда и только тогда, когда соответствующий
элемент произведения булевых матриц истинен.
\end{proof}


\section{Проверка результата алгоритма умножения матриц}\marginnote{20}
Итак, мы знаем уже несколько алгоритмов умножения матриц, но у нас 
нет хорошего способа проверки таких алгоритмов (и реализующих их
программ). Рассмотрим вероятностный 
алгоритм, который даст нам возможность \emph{проверять} результат
умножения матриц над \emph{полем} быстрее, чем \emph{вычислять} произведение.

Возьмем случайный вектор $\bf r$, т.е. вектор,
составленный из битов, принимающих значения 0 или 1 с вероятностью $\frac{1}{2}$
независимо друг от друга.
У нас уже есть результат перемножения $n\times n$ матриц $\bf A$ и $\bf B$ --- 
матрица $\bf C$, полученная при помощи алгоритма, в правильности которого мы
не уверены. Будем проверять равенство
\begin{equation}\label{eq:matrix}
        \bf A \cdot B = C.
\end{equation}

Домножим обе части справа на случайный вектор $\bf r$. Вместо 
(\ref{eq:matrix}) проверим новое равенство
\[
        ({\bf AB}) \cdot {\bf r} =  {\bf C} \cdot {\bf r}
\] 
и выдадим ответ, соответствующий результату этой проверки.
На такую проверку уйдет лишь $O(n^2)$ операций с элементами матриц ---
это меньше, чем в алгоритме Штрассена (и в любом другом известном алгоритме
для \emph{умножения} матриц).
Докажем, что этот алгоритм действительно проверяет результат 
перемножения.

\begin{theorem}
        $\forall$ матриц $\mathbf{A,B,C}$\\
        a) $\bf AB=C \Rightarrow$ алгоритм проверки не ошибается,\\
        b) $\bf AB \neq C \Rightarrow$ алгоритм ошибается с вероятностью 
        не более $\frac{1}{2}$. 
\end{theorem}
\begin{proof}
Пункт a) очевиден, рассмотрим пункт b). \\
Известно, что $\bf (AB-C)r \neq {\bf 0}$. В каком случае алгоритм ошибется? 
Если скажет, что $\bf AB=C$, то есть если 
$\bf (AB-C)r = {\bf 0}$. Возьмем строчку матрицы 
${\bf X}={\bf A}{\bf B}-{\bf C}$, 
не равную ${\bf 0}$ (она есть, поскольку
${\bf A}{\bf B} \neq {\bf C}$). 
Пусть $x_{kl}$ --- 
ненулевой элемент этой строчки. Тогда произведение $k$-ой строки на $\bf r$ 
выглядит так:
\begin{equation} \label{eq:E1}
        \sum_{i \in \{1,2,\ldots,\widehat l,\ldots\}}x_{ki}r_i 
        {\mbox{\LARGE{ $+$ }}} 
        x_{kl}r_l
        {\mbox{\LARGE{ $=$ }}}
        0,\quad \text{где } x_{kl} \neq 0.
\end{equation}
Обозначим 
\[
        c := - \frac{1}{x_{kl}}
             \sum_{i \in \{1,2,\ldots,\widehat l,\ldots\}}x_{ki}r_i.
\]
С какой вероятностью $r_l=c$? С вероятностью выбрать бит $r_l$
равным биту $c$, то есть с вероятностью $\frac{1}{2}$.
Следовательно, алгоритм ошибается с вероятностью не более $\frac{1}{2}$.
\end{proof}

Такой метод проверки называется \emph{методом отпечатков пальцев}
(fingerprinting).

Итак, наш алгоритм правильно решает задачу (т.е. говорит,
что данная ему программа верно вычисляет произведение $\bf A$ и $\bf B$), 
если ${\bf A}{\bf B}={\bf C}$, и ошибается 
(говорит <<верно>>, хотя на самом деле <<неверно>>) с вероятностью
не более $\frac{1}{2}$, если ${\bf A}{\bf B}\neq{\bf C}$.
Алгоритмы такого типа называются вероятностными алгоритмами 
с \emph{односторонней ограниченной вероятностью ошибки
(one-sided bounded error)}.
Какова реальная польза от такого алгоритма? На первый взгляд,
вероятность ошибки велика. Но если этот алгоритм повторить 100 раз
(100 --- это лишь константа!), то вероятность 
ошибки станет $\frac{1}{2^{100}}$, а это уже меньше
вероятности отказа вычислительной техники.

\begin{remark}
Мы не пользовались тем, что матрицы --- квадратные.
\end{remark}

%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%

\section{Нахождение пары ближайших точек на плоскости}\marginnote{20}
Задача: на плоскости заданы координаты $n\ge2$ точек $(x_i,y_i)$.
Найти две различные точки из числа заданных, 
находящиеся на минимально возможном расстоянии
(и определить это расстояние).

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

Разделим наше множество точек 
на два приблизительно равных по мощности:
первые $\lceil n/2\rceil$ элементов списка $X$ и оставшиеся.
(Сделать это, используя наши списки, просто --- получатся
такие же пары списков, только в два раза короче.)
Назовем эти множества $S_1$ и $S_2$;
имеется значение $x_0$ первой координаты, которое
разделяет элементы этих множеств.
Рекурсивно применим наш алгоритм к $S_1$ и к $S_2$ --- тем самым, 
найдем ближайшие пары точек для каждого из этих множеств.

Пусть наименьшее из полученных расстояний --- $\delta$.
Для завершения вычислений нам остается проверить случай,
когда ближайшая пара состоит из одной точки множества $S_1$
и одной точки множества $S_2$.
Если это так, расстояние между ними менее $\delta$, 
а значит, обе они находятся в вертикальной полосе 
с координатами от $x_0-\delta$ до $x_0+\delta$
(множество таких точек легко выделить при помощи списка $X$).

Проверим расстояния от каждой из
полученных точек до следующих семи точек в списке $Y$.
Заметим, что этого достаточно: искомая пара точек находится
внутри прямоугольника высоты $\delta$,
выделенного из нашей вертикальной полосы.
Этот прямоугольник состоит из двух квадратов со стороной $\delta$,
в каждом из них может быть не более четырех точек, иначе
в соответствующем множестве $S_i$ были бы точки,
расстояние между которыми было бы меньше $\delta$ (разделим
этот квадрат на четыре одинаковых квадратика --- в каждом из
них может быть только одна точка).

Рекуррентное неравенство для количества операций,
совершаемых нашим алгоритмом,
очевидно, $T(n)\le 2T(\lceil n/2\rceil)+O(n)$.
По теореме~\ref{th:recursion}, $T(n)=O(n\log n)$, 
и столько же операций используется
на построение исходных списков (поскольку их надо отсортировать).
(Заметим, что на перебор всех пар точек понадобилось бы
$\Omega(n^2)$ операций.)

\end{document}


