\documentclass[12pt,fleqn,a4paper]{book}
% 
% Packages used:
%
\usepackage[koi8-r]{inputenc}
\usepackage[russian,english]{babel}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{amsfonts}
\usepackage{amsthm}
%
% Common customization:
%
\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]
\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 (Конспект: {#3})}}
\thispagestyle{headings}}
%
% Things to customize for the course are here:
%
\newcommand\lecturername{Э. А. Гирш}
\newcommand\coursetitle{с/к ``Эффективные алгоритмы''}
%
% Now, this particular lecture definitions:
%
\newcommand{\bex}{\begin{example}\rm}
\newcommand{\eex}{\end{example}}
\newcommand{\ba}{\begin{algorithm}\rm}
\newcommand{\ea}{\end{algorithm}}
\newcommand{\bea}{\begin{eqnarray*}}
\newcommand{\eea}{\end{eqnarray*}}
\newcommand{\be}{\begin{eqnarray}}
\newcommand{\ee}{\end{eqnarray}}
\newcommand{\abs}[1]{\lvert#1\rvert}
%
% The document
%
\begin{document}
\selectlanguage{russian}
%
% Lecture title
%
\lecture{1}{Умножение матриц и его проверка. Обращение матриц. Сравнение
строк на расстоянии и поиск подстроки.}{Ф. Александров}
%
% The lecture
%
\section{Умножение матриц}
Будем учиться как можно быстрее перемножать квадратные 
матрицы с элементами из кольца. Пусть у нас есть две матрицы 
$\bf A$ и $\bf B$ размера $n\times n$. 
Обозначим за $\bf C$ их произведение: 
\be
        {\bf A} * {\bf B} = {\bf C}.
\ee 

\paragraph{1. Простой способ.}
Пусть $n=2^k, k \in \mathbb Z$. Поделим каждую из матриц на 4 равные части.
Например,
\be
        \bf{A} = \begin{pmatrix}
                                \bf A_{11} & \bf A_{12} \\
                                \bf A_{21} & \bf A_{22}
                \end{pmatrix}.
\ee

Каждая из матриц разбиения будет иметь размерность 
$\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 C}_{ij}$ опять поделим на четыре равные части,
и так далее, пока не сведем перемножение матриц к операциям 
перемножения элементов кольца. 

Подсчитаем время работы $T(n)$ такого алгоритма. Здесь и далее за единицу 
времени примем время операции с элементом матрицы. 
\be
 T(n) = 8 T\left(\frac{n}{2}\right) + cn^2, 
 \quad \text{где $c$ -- некоторая константа}.   
\ee

\begin{theorem} \label{theorem:T1}
        Пусть $T(n) = 8T\left(\frac{n}{2}\right) + cn^2$, 
        $n=2^k$, $k \in \mathbb Z$. Тогда $T(n) = O(n^3)$. 
\end{theorem}
\begin{proof}
        $T(n)=8T(\frac{n}{2}) + cn^2 = 
        8^2T(\frac{n}{4})+8c(\frac{n}{2})^2+cn^2 = \dots = \\
        cn^2+8c(\frac{n}{2})^2+8^2c(\frac{n}{2^2})^2+ \dots + 
        8^{k-1}c(\frac{n}{2^{k-1}})^2+8^k =
        cn^2\left(1+\frac{8}{2}+\frac{8^2}{2^2}+ \dots +
        \frac{8^{k-1}}{2^{2k-2}}\right)+8^k=
        cn^2\left(\frac{2^k-1}{3}\right)={c'}n^2(n-1)=O(n^3)$
\end{proof}

Из теоремы \ref{theorem:T1} следует, что время работы нашего алгоритма 
равно $O(n^3)$. То есть сам алгоритм не лучше алгоритма перемножения матриц
``по определению'', но этот же подход мы сейчас реализуем в алгоритме, 
трудоемкость которого будет уже меньше $O(n^3)$. Для этого нам потребуется
чуть более общая теорема:

\begin{theorem} \label{theorem:T2} 
Пусть $n=b^k$, $k \in \mathbb Z$, $T(n)=aT(\frac{n}{b})+cn^2$, $a<b^2$.
Тогда $T(n)=O(n^{log_ba})$.
\end{theorem}
\begin{proof}
Аналогично доказательству теоремы
\ref{theorem:T1}.
\end{proof}

\begin{exercise}
        Докажите теорему \ref{theorem:T2} для произвольного $n$.
\end{exercise}  

\begin{exercise}
        Докажите теорему \ref{theorem:T2} при $a=b^2$.
\end{exercise}

\begin{exercise}
        Докажите теорему \ref{theorem:T2} при $a>b^2$.
\end{exercise}


\paragraph{2. Способ похитрее (\emph{Алгоритм Штрассена}.}
Опять рассмотрим такое же разбиение матриц и введем новые матрицы
\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*}

Пользуясь теоремой \ref{theorem:T2}, находим время работы алгоритма: 
$T(n)=O(n^{log_27})$. Поскольку $log_27\approx 2.80735$, 
этот алгоритм лучше предыдущего 
и стандартного алгоритма (через вычисление каждого элемента результирующей
матрицы по определению умножения матриц). 

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

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

\section{Уножение булевых матриц}

Мы не можем использовать наш быстрый алгоритм для перемножения булевых матриц, 
так как $T$ (истина) и $F$ (ложь) 
с операциями $\vee$ (дизъюнкция) и $\wedge$ (конъюнкция) не образуют кольца.

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


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

Возьмем случайный вектор $\bf r$, составленный из случайных битов (принимают 
и 1, и 0 с равными вероятностями). 
У нас уже есть результат перемножения матриц $\bf A$ и $\bf B$ -- 
матрица $\bf C$, полученная нами. Будем проверять равенство:
\be
        \bf A \times B = C.
\ee     

Домножим обе части справа на случайный вектор $\bf r$. Теперь проверим 
новое равенство
\be
        \bf AB \times r =  C \times r.
\ee 
На проверку его уйдет меньше времени, трудоемкость такой проверки 
равна $O(m*n)$, где $n$ -- длина вектора $\bf r$, $m$ -- количество строк
матрицы $\bf C$. Если $\bf C$ квадратная, то трудоемкость выражается проще --
$O(n^2)$. Вспомним, что трудоемкость при перемножении матриц была близка к 
$O(n^{2.8})$. Докажем, что этот алгоритм дествительно проверяет результат 
перемножения.

\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 \emptyset$. В каком случае алгоритм ошибется? 
Если скажет, что $\bf AB=C$, то есть если 
$\bf (AB-C)r = \emptyset$. Возьмем строчку матрицы 
${\bf X}={\bf A}{\bf B}-{\bf C}$, 
не равную $\emptyset$ (помним, что сейчас у нас
${\bf A}{\bf B} \neq {\bf C}$). 
Пусть $x_{kl}$ -- 
ненулевой элемент этой строчки. Тогда произведение $k$-ой строки на $\bf r$ 
выглядит так:
\be \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.
\ee
Обозначим 
\be
        c := - \frac{1}{x_{kl}}
             \sum_{i \in \{1,2,\ldots,\widehat l,\ldots\}}x_{ki}r_i.
\ee
С какой вероятностью $r_l=c$? С вероятность выбрать бит $r_l$
равным биту $c$, то есть с вероятностью $\frac{1}{2}$.
Следовательно, алгоритм ошибается с вероятностью не более $\frac{1}{2}$.
\end{proof}

Такой метод проверки называется {\it 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)}.
Какова реальная польза от такого алгоритма? Ведь вероятность ошибки
очень велика. Но если этот алгоритм применить 10 раз, то вероятность 
ошибки станет $\left(\frac{1}{2}\right)^{10}$, а это уже менее 0.001.

\section{Обращение матриц}
Конечно же, мы хотим применить наш (и любой другой)
быстрый алгоритм перемножения 
матриц для обращения матрицы. (Естественно, над произвольным кольцом
обратить матрицу не выйдет, ибо понадобится обратный элемент.)

\paragraph{1. Обращение треугольных матриц.}
Рассмотрим квадратную матрицу $\bf A$:
\begin{eqnarray*}
        \bf A=\begin{pmatrix} \bf A_{11} & \emptyset \\
                          \bf A_{21} & \bf A_{22} 
          \end{pmatrix}, 
        \quad \text{где } \bf A_{21} \text{ -- квадратная матрица,} \\ 
        \text{ а } \bf A_{11}, A_{22} \text{ -- квадратные треугольные}. 
\end{eqnarray*}               

Тогда обратной к матрице $\bf A$ будет матрица такого вида
\be
        \bf A^{-1}=\begin{pmatrix} \bf A_{11}^{-1} & \emptyset \\
                              \bf -A_{22}^{-1}A_{21}A_{11}^{-1} & \bf A_{22}^{-1}
               \end{pmatrix}            
\ee

Как видно, получили рекурсивный алгоритм обращения треугольных матриц. 

\begin{exercise}\label{ex:triangle}
Посчитать трудоемкость алгоритма, зная трудоемкость 
$M(n)=O(n^{2+\varepsilon})$ умножения $n\times n$-матриц
(для некоторого $\varepsilon>0$).
\end{exercise}

\paragraph{2. Обращение произвольных матриц.}
\begin{theorem}
$\forall \bf A$ -- невырожденная матрица -- раскладывается в произведение
        \begin{eqnarray*}
                \bf A=LUP,
         \quad \text{где }
         \quad \bf L \text{ -- нижняя треугольная матрица}, \\
          \quad \bf U \text{ -- верхняя треугольная матрица}, \\
          \quad \bf P \text{ -- матрица перестановок}.
        \end{eqnarray*}
Причем раскладывается достаточно быстро. 
\end{theorem} 

\begin{proof}
Докажем, предъявив алгоритм. 
Пусть $\bf A$ -- $m \times p$ матрица, для которой хотим найти обратную.
Алгоритм наш будет рекурсивным, с уменьшением порядка матрицы на каждом 
шаге рекурсии. Рекурсия остановится на тривиальном случае.

В алгоритмическом плане это выглядит так, будто мы умеем находить искомое 
разложение для матриц меньшего, чем у $\bf A$, размера и хотим свести задачу 
к разложениям таких матриц. 

Такой (рекурсивный) алгоритм разложения матрицы $\bf A$ размером $m \times p$ 
назовем $factor(\bf A,m,p)$. Он возвращает три необходимые матрицы
$\bf L,U,P$. Предъявим шаг рекурсии для размера $m \times p$.
Разделим $\bf A$ на две матрицы:
\bea
        \bf A=\begin{pmatrix} \bf B \\
                              \bf C
                \end{pmatrix}
\eea
где ${\bf B}$ и ${\bf C}$ -- матрицы размера $m/2 \times p$.

Матрицы $\bf L_1,U_1,P_1$ -- результат $factor({\bf B},m/2,p)$ (то
есть $\bf B=L_1U_1P_1$). 

Введем новые матрицы:
\bea
        \bf D=CP_1^{-1} \\
        \bf G=D-FE^{-1}U_1 \\
        \bf H=U_1P_3^{-1}
\eea

Пусть $\bf G'$ -- матрица, образованная $p-\frac{m}{2}$ последними столбцами 
матрицы $\bf G$. Получим $\bf L_2,U_2,P_2$, как результат 
$factor({\bf G'},\frac{m}{2},p-\frac{m}{2})$. Далее, за $\bf E$
обозначим первые $m/2$ столбцов матрицы $\bf U_1$,
а за $\bf F$ -- 
первые $m/2$ столбцов матрицы $\bf D$,
За $\bf P_3$ примем матрицу, у которой нижним правым блоком является $\bf P_2$,
верхним левым -- единичная размера $m/2 \times m/2$, дополненную также нулями. 

Теперь получим наши искомые матрицы $\bf L,U,P$:

Матрицу $\bf L$ составим из матриц $\bf L_1$ (верхний левый блок), 
$\bf FE^{-1}$ (нижний левый), $\bf L_2$ (нижний правый). Оставшимися 
элементами запишем нули. 

$\bf U$ построим с помощью $\bf H,U_2$: верхняя ее часть -- это $H$,
нижняя же состоит из нулевой матрицы слева и $U_2$ справа. 

$\bf P=P_3P_1$.
\end{proof}

Очевидно, что для $\bf A=LUP$ $\bf A^{-1}=P^{-1}U^{-1}L^{-1}$.
Искать обратные для треугольных матриц $\bf L,U$ уже умеем, 
а для перестановочной матрицы $\bf P$ обратной будет ${\bf P}^{\text T}$.  

\begin{exercise}\label{ex:lup}
Посчитайте трудоемкость описанного выше алгоритма разложения матрицы
в терминах упражнения~\ref{ex:triangle}.
\end{exercise}

\begin{theorem}\label{th:inverse}
В условиях упражнения~\ref{ex:triangle}
можно выполнить обращение произвольной невырожденной
$n\times n$ матрицы за время $M(n)$.
\end{theorem}

\begin{exercise}\label{ex:inverse}
Доказать теорему~\ref{th:inverse}. \emph{Указание:} воспользоваться
упражнениями~\ref{ex:triangle} и \ref{ex:lup};
обращение матрицы перестановки можно быстро реализовать,
представив ее в виде одномерного массива.
\end{exercise}

\section{Метод fingerprinting в применении к задачам со строками}
\paragraph{1. Сравнение на равенство двух строк.}
Есть две строки $a,b$ (можно считать их битовыми), которые необходимо 
сравнить на совпадение, затратив как можно меньше информации на это сравнение.
Например, надо сравнить файлы по сети. 

Идея алгоритма -- сравнивать не сами строки, а функции от них. Пусть 
длина $\sharp a$ строки $a$ составляет $n$ бит.

Пусть $p \in \mathbb P$, $\mathbb P$ -- множество простых чисел. В качестве 
функции-хэша возьмем $\mod p$. То есть будем сравнивать уже
\be \label{eq:E2}
        a \mod p \quad \text{и} \quad b \mod p
\ee
Для такого сравнения достаточно передать $\log p$ битов (здесь 
и далее по-умолчанию берется двоичный логарифм, $log_2$)
и еще столько же битов понадобится для передачи числа $p$.

Будем брать случайное простое число $p$ 
из интервала $[2 .. \tau]$ для некоторого $\tau$, которое определим позже. 
Плохими $p$ для 
нас будут такие, которые будут давать равенство в (\ref{eq:E2}) при неравенстве 
исходных строк $a,b$. Количество таких чисел равно
\be
        \sharp\{p \in P: (a-b) \vdots p\}
\ee

\begin{problem}
Как выбрать простое число из заданного интервала случайным образом
с равномерным распределением?
\end{problem}

\begin{lemma}
        $c \leq 2^n \Rightarrow c \text{ имеет не более $n$ различных простых делителей}.$
\end{lemma}
\begin{proof}
Очевидно.
\end{proof}

Пусть $\tau = n^2 \log n^2$. Тогда вероятность ошибки при сравнении 
остатков $\mod p$ можно оценить
\be
        P_{\text{ошибки}} \leq \frac{n}{\frac{\tau}{\log \tau}}=
             \frac{n(\log n^2 + \log \log n^2)}{n^2\log n^2}=
                        O\left(\frac{1}{n}\right).
\ee 

Таким образом, привели вероятностный алгоритм с односторонней ошибкой
с вероятностью ошибки $O(\frac{1}{n})$, требующий передачи
всего $O(\log n)$ битов. 

\begin{definition}
        \emph{Расстоянием} между двумя строками $a,b$ будем считать 
        количество несовпадающих у них битов.
\end{definition}

\begin{problem}
        Придумать алгоритм для нахождения расстояния $d$ между 
        двумя строками $a,b$ длины $n$. Посчитать количество
        передаваемых битов, как функцию 
        $T(n,d)$.
\end{problem}
        
\begin{definition}
        Под \emph{editing distance} 
        между $a,b$ будем понимать минимальное количество операций 
        редактирования, необходимых для преобразования строки $a$ в строку $b$.
        Операциями редактирования считаем:

                1. вставку бита

                2. замену бита

                3. уничтожение бита

                4. перемещение сплошного блока битов 
\end{definition}

\begin{problem}
        Придумать алгоритм для нахождения editing distance между 
        двумя строками $a,b$ длины $n$. Посчитать трудоемкость $T(n,d)$.
\end{problem}
        

\paragraph{2. Поиск вхождения строки $a$ в строку $b$.}

Займемся теперь такой задачей. Нужно определить, входит ли 
строка $a$ в строку $b$. Длины строк $a$ и $b$ равны, соответственно,
$m$ и $n$. Пусть $m \le n$. Ясно, что решая ее в лоб, получим сложность
почти $O(mn)$.

Предъявим вероятностный алгоритм с линейной сложностью $O(m+n)$. 

\begin{remark}
        Существует детерминированный алгоритм поиска подстроки в строке 
        за время $O(m+n)$, но он гораздо сложнее.
\end{remark}

Определим
\be
        b(i)=b_ib_{i+1} .. b_{i+m-1}.
\ee

Необходимо провести $n-m+1$ сравнений строк на равенство, а это мы уже умеем делать: будем сравнивать
\be
        (a \mod p) \quad \text{и} \quad (b(i) \mod p).
\ee

Но можно упростить задачу, вычисляя $(b(i) \mod p)$ через $(b(i-1) \mod p)$.
\bea
        b(i)=b_i + 2b_{i+1} + .. + 2^{m-1}b_{i+m-1} \\
        b(i-1)=2b_i + 2^2b_{i+1}+ .. +2^{m-1}b_{i+m-2}+b_{i-1} \\
        \Rightarrow b(i)=\frac{b(i-1)-b_{i-1}}{2}+2^{m-1}b_{i+m-1} \\
\eea

Опять будем брать простое число $p \in [2,\tau]$. $\tau = n^2m\log n^2m$,
\be
        P_{\text{ошибки}}\leq
                \frac{m}{\frac{\tau}{\log \tau}} \leq
                \frac{2m\log n^2m}{n^2m \log n^2m} =
                        O\biggl(\frac{1}{n^2}\biggr)
\ee

Можно вообще избавить этот алгоритм от необходимости ошибаться. Если 
\bea
        a \mod p = b(i) \mod p,
\eea
 то честно
проверим равенство $a=b$. Этот алгоритм уже не ошибается. 
Какова его сложность? 
Математическое ожидание времени его работы $T(n,m)$ легко посчитать:
\be
                {\bf E}T(n,m) \leq mn*\frac{1}{n}+(m+n)(1-\frac{1}{n})=
                        O(m+n).
\ee

То есть предьявили искомый линейно-быстрый алгоритм поиска подстроки в строке,
причем это \emph{вероятностный алгоритм с нулевой ошибкой}.

\begin{exercise}
        Исследовать работу (вероятность ошибки и сложность) такого алгоритма:
        если $(a-b(i))\vdots p$, то выбираем новое простое $p'$ и меняем 
        $p$ на $p'$. 
\end{exercise}

\begin{problem}
        Обобщить алгоритмы на случай двумерного пространства. 
        То есть реализовать поиск блока в матрице.
\end{problem}

\end{document}
