'''Арифметическое кодирование''' (англ. ''Arithmetic coding'') Convex hull trick {{---}} алгоритм сжатия информации без потерь, который при кодировании ставит в соответствие тексту вещественное число один из отрезка <tex>методов оптимизации динамического программирования [[0; 1)<http://tex>neerc.ifmo. Данный метод, как и [[Алгоритм Хаффмана|алгоритм Хаффмана]], является [[Энтропия случайного источника|энтропийнымru/wiki/index.php?title=Динамическое_программирование]], тиспользующий идею выпуклой оболочки.е. длина кода конкретного символа зависит от частоты встречаемости этого символа в тексте. Арифметическое кодирование показывает более высокие результаты сжатияПозволяет улучшить ассимптотику решения некоторых задачь, чем алгоритм Хаффмана, для данных решемых методом динамического программирования с неравномерными распределениями вероятностей кодируемых символов. Кроме того, при арифметическом кодировании каждый символ кодируется нецелым числом бит, что эффективнее кода Хаффмана (теоретически, символу <texmath>aO(n^2)</texmath> с вероятностью появления до <tex>pO(n\cdot\log(an))</tex> допустимо ставить . Техника впервые появилась в соответствие код длины <tex>1995 году (задачу на нее предложили в USACO {{---\log_2 p}} национальной олимпиаде США по программированию). Массовую известность получила после IOI (aмеждународной олимпиады по программированию для школьников)</tex>, следовательно, при кодировании алгоритмом Хаффмана это достигается только с вероятностями, равными обратным степеням двойки).2002. == Принцип действия Пример задачи, решаемой методом convex hull trick===== Кодирование === Рассмотрим задачу на ДП:На вход алгоритму передаются текст для кодирования и список частот встречаемости символов. {{Задача# Рассмотрим отрезок |definition = Есть <texmath>[0; 1)n</math> деревьев с высотами <tex> на координатной прямой. # Поставим каждому символу текста в соответствие отрезокa_1, длина которого равна частоте его появления.# Считаем символ из входного потока и рассмотрим отрезокa_2, соответствующий этому символу. Разделим этот отрезок на части\dots, пропорциональные частотам встречаемости символов.# Повторим пункт a_n</tex> (3в метрах) до конца входного потока.Требуется спилить их все, потратив минимальное количество монет на заправку# Выберем любое число из получившегося отрезка бензопилы. Но пила устроена так, которое и будет результатом арифметического кодированиячто она может спиливать только по 1 метру от дерева, к которому ее применили.Также после срубленного метра (любого дерева) пилу нужно заправлять, платя за бензин определенной кол-во монет. Причем стоимость=== Псевдокод === * бензина зависит от срубленных (полностью) деревьев. Если сейчас максимальный индекс срубленного дерева равен <mathtex>\mathtt{s}\,i</mathtex> {{---}} текст, подаваемый на вход;, то цена заправки* равна <mathtex>\mathtt{n}\,c_i</mathtex> {{---}} длина исходного текста;. Изначально пила заправлена.* Также известны следующие ограничения : <mathtex>\mathtt{m}\c_n = 0, a_1 = 1,a_i</mathtex> {{---}} мощность алфавита исходного текста;*возрастают, <mathtex>\mathtt{letters[m]}\,c_i</mathtex> {{---убывают. Изначально пила заправлена. (убывание и возрастание нестрогие) }} массив символов, составляющих алфавит исходного текста;* (Задача H с Санкт-Петербургских сборов к РОИ 2016[http://neerc.ifmo.ru/school/camp-2016/problems/20160318a.pdf]) <math/noinclude>\mathtt{probability[m]}\, </mathincludeonly> {{---}} массив вероятностей обнаружения символа в тексте; *<math>\mathtt#if: {Segment}\,</math> {{---neat|}}} структура, задающая подотрезок отрезка | <texdiv style="background-color: #fcfcfc; float:left;">[0 <div style="background-color: #ddd; 1)">'''Задача:'''</texdiv>, соответствующего конкретному символу на основе частотного анализа. Имеет поля:** <mathdiv style="border:1px dashed #2f6fab; padding: 8px; font-style: italic;">\mathtt{left}\,</math> {{---definition}}} левая граница подотрезка;**<math/div>\mathtt{right}\, </mathdiv> {{---}} правая граница подотрезка;|* <math>\mathtt{left}\,table border="0" width="100%"> </mathtr>, <mathtd style="background-color: #ddd">\mathtt{right}\,'''Задача:'''</mathtd> {{-</tr> <tr><td style="border:1px dashed #2f6fab; padding: 8px; background-color: #fcfcfc; font-style: italic;">{{{definition}}} границы отрезка, содержащего возможный результат арифметического кодирования.</td></tr> </table>}} <code/includeonly> '''struct''' Segment: ==Наивное решение== '''double''' left '''double''' right '''Segment'''[m] defineSegments(letters Сначала заметим важный факт : '''char'''т.к. <tex>c[m], probability: '''double'''[mi]</tex> убывают (нестрого): '''Segment'''и <tex>c[mn] segment '''double''' l = 0 '''for''' i = 0 '''to''' m - 1 segment[letters= 0</tex>, то все <tex>c[i]]</tex> неотрицательны.left = l segment[letters[i]] Понятно, что нужно затратив минимальную стоимость срубить последнее (<tex>n</tex>-е) дерево, т.к. после него все деревья можно будет рубить бесплатно (т.к.right = l + probability<tex>c[in] l = segment[letters[i]]0</tex>).right '''return''' segment '''double''' arithmeticCoding(lettersПосчитаем следующую динамику : '''char'''<tex>dp[mi]</tex> {{---}} минимальная стоимость, заплатив которую можно добиться того, probabilityчто дерево номер <tex>i.</tex> будет срублено. База динамики : '''double'''<tex>dp[m1]= 0</tex>, s: '''char'''[n])т.к. изначально пила заправлена и высота первого дерева равна 1, по условию задачи. Переход динамики : '''Segment'''[m] segment = defineSegments понятно, что выгодно рубить сначала более дорогие и низкие деревья, а потом более высокие и дешевые (lettersдок-во этого факта оставляется читателям как несложное упражнение, т.к. эта идея относится скорее к теме жадных алгоритмнов, probabilityчем к теме данной статьи) '''double''' left = 0 '''double''' right = 1 '''for''' . Поэтому перед <tex>i</tex>-м деревом мы обязательно срубили какое-то <tex>j</tex>-е, причем <tex>j \leqslant i = 0 '''to''' n - 1 '''char''' symb = s</tex>. Поэтому чтобы найти <tex>dp[i] '''double''' newRight = left + (right </tex> нужно перебрать все <tex>1 \leqslant j \leqslant i - left) * segment[symb]1</tex> и попытаться использовать ответ для дерева намер <tex>j</tex>.right '''double''' newLeft = left + (right - left) * segment[symb].left left = newLeft right = newRight '''return''' (left + right) / 2Итак, пусть перед <tex>i</codetex> '''Замечание:''' для оптимизации размера кода можно выбрать из полученного на последнем шаге диапазона -м деревом мы полностью срубили <tex>j</tex>-е, причем высота <tex>i</tex>-го дерева составляет <tex>a[left; righti]</tex> число, содержащее наименьшее количество знаков в двоичной записиа т.к. === Декодирование ===Алгоритм по вещественному числу восстанавливает исходный текст.# Выберем на отрезке последнее дерево, которое мы срубили имеет индекс <tex>j</tex>, то стоимость каждого метра <tex>i</tex>-го дерева составит <tex>c[0; 1)j]</tex>, разделенном . Поэтому на части, длины которых равны вероятностям появления символов в тексте, подотрезок, содержащий входное вещественное числосруб <tex>i</tex>-го дерева мы потратим <tex>a[i] \cdot c[j]</tex> монет. СимволТакже не стоит забывать, соответствующий этому подотрезкуситуацию, дописываем в ответ.# Нормируем подотрезок и вещественное число.# Повторим пункты 1{{когда <tex>j</tex>---}}2 до тех поре дерево полностью срублено, пока мы получили не получим ответбесплатно, а за <tex>dp[j]</tex> монет. Итогвая формула пересчета : <tex>dp[i] === Псевдокод === *<math>\mathttmin\limits_{codej=1...i-1}(dp[j] + a[i] \,cdot c[j])</mathtex> {{---}} вещественное число, подаваемое . Посмотрим на вход;код выше описанного решения:* '''int''' <mathtex>\mathtt{nsimpleDP}\,</mathtex> {{---}} длина восстанавливаемого текста;*<math>\mathtt{m}\,</math> {{---}} мощность алфавита исходного текста;*<math>\mathtt{letters('''int''' a[mn]}\,</math> {{---}} массив символов, составляющих алфавит исходного текста;'''int''' c[n]) dp[1] = 0* dp[2] = dp[3] = ... = dp[n] = <mathtex>\mathtt{probabilityinfty</tex> '''for''' i = 1..n-1 dp[mi]}= <tex>+\,infty</mathtex> {{---}} массив вероятностей обнаружения символа в тексте; * '''for''' j = 0..i-1 '''if''' (dp[j] + a[i] <mathtex>\mathtt{segment}\,cdot</mathtex> {{---}} структура, задающая подотрезок отрезка c[j] < dp[i]) dp[i] = dp[j] + a[i] <tex>[0; 1)\cdot</tex>, соответствующего конкретному символу на основе частотного анализа. Имеет поля:** c[j] '''return''' dp[n] Нетрудно видеть, что такая динамика работает за <mathtex>\mathtt{left}\,O(n^2)</mathtex> {{---}} левая граница подотрезка;. ==Ключевая идея оптимизации==** Для начала сделаем замену обозначений. Давайте обозначим <mathtex>\mathtt{right}\,dp[j]</mathtex> {{---}} правая граница подотрезка;** за <mathtex>b[j]</tex>\mathtt{character}\,<tex>a[i]</mathtex> {{---}} значение символа. за <codetex>x[i]</tex>, а <tex>c[j]</tex> за <tex>k[j]</tex>. '''struct''' Segment: '''double''' left '''double''' right '''char''' character '''Segment''' Теперь формула приняла вид <tex>dp[mi] defineSegments= \min\limits_{j=0...i-1}(letters: '''char'''k[nj], probability: '''double'''\cdot x[i] + b[nj]): '''Segment'''</tex>. Выражение <tex>k[mj] segment '''double''' l \cdot x + b[j]</tex> {{---}} это в точности уравнение прямой вида <tex>y = 0kx + b</tex>. '''for''' i = 0 '''to''' m - 1 segment Сопоставим каждому <tex>j</tex>, обработанному ранее, прямую <tex>y[ij].left (x) = l segmentk[ij].right = l \cdot x + probabilityb[i] segment[ij]</tex>.character = lettersИз условия «<tex>c[i] l = segment</tex> убывают <tex>\Leftrightarrow k[ij]</tex> уменьшаются с номером <tex>j</tex>» следует то, что прямые, полученные ранее отсортированы в порядке убывания углового коэффициент.rightДавайте нарисуем несколько таких прямых : '''return''' segment [[Файл:picture1convexhull.png]] '''string''' arithmeticDecoding Выделим множество точек <tex>(letters: '''char'''[m]x0, y0)</tex> , probability: '''double'''[m]таких что все они принадлежат одной из прямых и при этом нету ни одной прямой <tex>y’(x)</tex>, code: '''double''', n: '''int'''такой что <tex>y’(x0): '''Segment'''[m] segment < y0</tex>. Иными словами возьмем «выпуклую (вверх) оболочку» нашего множества прямых (её еще называют нижней ошибающей множества прямых на плоскости). Назовем ее «<tex>y = defineSegmentsconvex(lettersx)</tex>». Видно, probabilityчто множество точек <math>(x, convex(x) )</math> представляет собой выпуклую вверх функцию. '''string''' s ==Цель нижней огибающей множества прямых== "" '''for''' Пусть мы считаем динамику для <tex>i = 0 '''to''' n </tex>- 1 '''for''' го дерева. Его задает <tex>x[i]</tex>. Итак, нам нужно для данного <tex>x[i]</tex> найти <tex>\min\limits_{j = 0 '''to''' m ..i- 1 '''if''' code >= segment}(k[j].left '''and''' code < segment\cdot x[ji].right s += segmentb[ji].character code ) = (code – segment[\min\limits_{j]=0..left) / i-1}(segmenty[j](x[i]))</tex>.right – segmentЭто выражение есть <math>convex(x[ji].left) '''break''' '''return''' s</codemath> '''Замечание:''' кодировщику . Из монотонности угловых коэффицентов отрезков, задающих выпуклую оболочку, и декодировщику должно быть известноих расположения по координаты x следует то, что отрезок, который пересекает прямую <tex>x = x[i]</tex>, когда завершать работу. Для этого можно передавать в качестве аргумента длину текста или символ конца файла, после которого процесс должен быть остановленнайти бинарным поиском. '''Замечание:''' Несмотря на преимущества арифметического кодирования, существует проблема при его практическом применении из-за несовершенства представления чисел с плавающей точкой в памяти компьютера {{---}} поскольку некоторые дробные числа не могут быть точно представлены в двоичном коде, используемом современными процессорами Это потребует <tex>O(\log(например, n))</tex>\dfrac{1}{3}времени на поиск такого </tex>), границы символов будут округлены, что может повлечь за собой неверную работу алгоритма при больших объёмах данных. В общем случае, алгоритм можно модифицировать так, чтобы результатом было дробное число. В такой реализации вероятность встречи символа представляется в виде рационального числа. Поскольку в каждой итерации будет переход из текущего отрезка в один из его <tex>mj</tex> подотрезков, кратных по длине что <tex>n</texdp[i] = k[j] \cdot x[i] + b[j]</tex>, а всего итераций . Теперь осталось научиться поддерживать множество прямых и быстро добавлять <tex>ni</tex>-ю прямую после того, в конечном результате знаменатель дроби не превысит как мы посчитали <tex>n^{n}b[i] = dp[i]</tex>, а поскольку сумма всех вероятностей встречи символов равна . Воспользуемся идеей алгоритма построения выпуклой оболочки множества точек. Заведем 2 стека <tex>1k[]</tex>, полученная дробь будет находиться в промежутке и <tex>b[0; 1)]</tex>, которые задают прямые в отсортированном порядке их угловыми коэффицентами и свободными членами. == Пример работы ==Рассмотрим в качестве примера строку ситуацию, когда мы хотим добавить новую (<tex>abacabai</tex>:=== Кодирование ==={|class="wikitable"!Символ||Частота появления|-|-тую) прямую в множество. Пусть сейчас в множестве лежит <p style="text-align:center;"tex>sz</tex>aпрямых (нумерация с 1). Пусть </tex>(xL, yL)</ptex>||<p style="text{{---align:center;">}} точка пересечения <tex>0.571429sz - 1</tex></p>|-|<p style="text-align:center;">й прямой множества и <tex>bsz</tex>-й, а </ptex>||(xR, yR)<p style="text/tex> {{---align:center;"}} точка пересечения новой прямой, которую мы хотим добавить в конец множества и <tex>sz</tex>0-й.285714Нас будут интересовать только их </tex>x</ptex>|-|овые координаты <tex>xL<p style="text-align:center;"/tex>и <tex>cxR</tex>, соответственно. Если оказалось, что новая прямая пересекает <tex>sz</ptex>||-ю прямую выпуклой оболочки позже, чем <p style="text-align:center;"tex>sz</tex>0.142857-я </tex>sz - 1</ptex>|}[[Файл:Code_png-ю, т.е.png|thumb|right|200px|Пример работы кодировщика ]]{|class="wikitable"!Считанный символ||Левая граница отрезка||Правая граница отрезка|-|||<p style="text-align:center;"><tex>0(xL \geqslant xR)</tex>, то <tex>sz</ptex>||-ю удалим из нашего множества, иначе {{---}} остановимся. Так будем делать, пока либо кол-во прямых в стеке не станет равным 2, либо <p style="text-align:center;"tex>xL</tex>1не станет меньше </tex>xR.</ptex>|-|<p style="text-align Асимптотика :center;">аналогично обычному алгоритму построения выпуклой оболочки, каждая прямая ровно <texmath>a1</texmath>раз добавится в стек и максимум </pmath>||1<p style="text-align:center;"/math>раз удалится. Значит время работы перестройки выпуклой оболочки займет <tex>0O(n)</tex></p>||<p style="text-align:center;"><tex>0суммарно.571429</tex></p>|- |<p style="text-align [[Файл:center;"><tex>b</tex></p>picture2convexhull.png]] [[Файл:picture3convexhull.png]] {{Теорема |id=th1239. |<p stylestatement="text-align:center;"><tex>0Алгоритм построения нижней огибающей множества прямых корректен.326531 |proof=Достаточно показать, что последнюю прямую нужно удалить из множества т.и т.т., когда она наша новая прямая пересекает ее в точке с координатой по оси X, меньшей, чем последняя {{---}} предпоследнюю. Пусть </tex>Y(x) = Kx + B</ptex>||{{---}} уравнение новой прямой, <p styletex>y[i](x) ="text-align:center;">K[i]x + B[i]</tex>0{{---}} уравнения прямых множества. Тогда т.к.489796</tex>K </p>|-|K[sz]<p style="text-align:center;"/tex>, то при <tex>ax \in [- \infty; xR] : y[sz](x) <= Y(x)</tex>, а т.к. </ptex>||K[sz] <p style="textK[sz -align:center;"1]</tex>, то при <tex>0.326531x \in [xL; + \infty] : y[sz - 1](x) \geqslant y[sz](x)</tex>. Если </ptex>||xL <p style="text-align:center;">xR</tex>0.419825, то при </tex>x \in [xL; xR] : y[sz - 1] \geqslant y[sz](x) и Y(x) \geqslant y[sz](x)</p>|-|<p style="text-align:center;"tex>, т.е. на отрезке <tex>c[xL; xR]</tex>прямая номер sz лежит ниже остальных и её нужно оставить в множестве. Если же </ptex>||<p style="text-align:center;"xL >xR</tex>0.406497, то она ниже всех на отрезке </tex>[xL; xR] = \varnothing </ptex>||<p style, т.е. её можно удалить из множества }} ==Детали реализации:=="text-align Будем хранить 2 массива :center;"><tex>0.419825front[]</tex></p{{---}} <tex>|x</tex>-координаты, начиная с которых прямые совпадают с выпуклой оболочкой (т.е. i-|я прямая совпадает с выпуклой оболочкой текущего множества прямых при <p style="text-align:center;"tex>x</tex>a</tex>\in</p>||<p style="text-align:center;"tex><tex>0.406497[front[i]; front[i + 1])</tex></p>||<p style="text-align:center;">) и <tex>0.414113st[]</tex>{{---}} номера деревьев, соответствующих прямым (т.е. <tex>i</ptex>|-|я прямая множества, где <p style="text-align:center;"tex>i</tex>b</tex>\in</ptex>||<p style="text-align:centertex>[1;">sz]</tex>0.410849соответствует дереву номер </tex>sz[i]</p>||<p style="text-align:center;"><tex>0).413025Также воспользуемся тем, что </tex>x[i] = a[i]</ptex>|-|возрастают (по условию задачи), а значит мы можем искать первое такое <p style="text-align:center;"tex>j</tex>a, что </tex>x[i] \geqslant front[j]</ptex>||не бинарным поиском, а методом двух указателей за <p style="texttex>O(n)</tex> операций суммарно. Также массив front[] можно хранить в целых числах, округляя х-align:center;">координаты в сторону лежащих правее по оси x до ближайшего целого (*), т.к. на самом деле мы, считая динамику, подставляем в уравнения прямых только целые <tex>0.410849x[i]</tex>, а значит если </ptex>||k<p style="text/tex>-align:center;">я прямая пересекается с <tex>0.412093k+1</tex></p>|}Код: -й в точке <tex>0.411471z +</tex> === Декодирование ===Код: <tex>0.411471\alpha</tex>[[Файл:decode1_png.png|thumb|right|200px|Пример работы декодировщика ]]{|class="wikitable"!Декодируемый символ||Код|-|(<math>z<p style="text/math>-align:center;">целое, <tex>a\alpha</tex> </ptex>||\in<p style="text-align:center;"/tex><tex>[0.411471;1)</tex>), то мы будем подставлять в их уравнения </ptex>|-|z<p style="text-align:center;"/tex> или <tex>bz + 1</tex>. Поэтому можно считать, что новая прямая начинает совпадать с выпуклой оболочкой, начиная с </ptex>||<p stylex ="text-align:center;"><tex>0.720074z+1</tex></p>|- |<p style ==Реализация=="text-align:center;"> '''int''' <tex>a\mathtt{ConvexHullTrick}</tex></p>||<p style="text-align:center;">('''int''' a[n], '''int''' c[n]) st[1] = 1 from[1] = -<tex>0.520259\infty</tex><font color=green>/p/ первая прямая покрывает все x-ы, начиная с -∞ </font>|-| sz = 1 <p stylefont color="text-align:center;"><tex>c<green>//tex>текущий размер выпуклой оболочки </pfont>|| pos = 1 <p stylefont color="text-align:center;"green><tex>0.910454</tex></p>|-|<p style="text-align:center;"><tex>aтекущая позиция первого такого j, что x[i] \geqslant front[st[j]] </texfont ></p>||<p style '''for''' i ="text-align:center;">2..n '''while''' (front[pos] < x[i]) <texfont color=green>0.373178</tex></p>|-|<p style=метод 1 указателя (ищем первое pos, такое что x[i] покрывается "областью действия"textst[pos]-align:center;">той прямой <tex/font >b pos = pos + 1 j = st[pos] dp[i] = K[j]</texmath>\cdot</pmath>||a[i] + B[j] '''if''' (i < n) <p stylefont color="text-align:center;"green><tex>0.653061</tex>/ если у нас добавляется НЕ последняя прямая, то придется пересчитать выпуклую оболочку </pfont >|-| K[i] = c[i] <p stylefont color="text-align:center;"green>// наши переобозначения переменных <tex/font >a B[i] = dp[i] <font color=green>/tex>/ наши переобозначения переменных </pfont >||<p style x ="text-align:center;"><tex>0.285714\infty</tex></p>|} '''while''''Замечание:'true'' при декодировании текста можно не только нормализовывать рабочий отрезок и текущий код, но и уменьшать рабочий отрезок (аналогично кодированию), не изменяя значение кода. j =st[sz] x == Декодирование divide(второй способ)=B[j] - B[i], K[i] - K[j]) <font color==Код: <texgreen>0.411471// x-координата пересечения с последней прямой оболочки, округленное в нужную сторону (*) </texfont > '''if''' (x > from[[Файл:decode2_png.png|thumb|right|200px|Пример работы декодировщика (второй способsz]) ]]{|class'''break''' <font color=green>// перестаем удалять последнюю прямую из множества, если новая прямая пересекает ее позже, чем начинается ее "wikitableобласть действия"</font >!Декодируемый символ||colspan sz ="4" |Границы отрезка|-|sz - 1<p stylefont color="text-align:center;"green>// удаляем последнюю прямую, если она лишняя <tex/font >a st[sz + 1] = i from[sz + 1] = x <font color=green>/tex>/ добавили новую прямую </p>||<p style="text-align:center;"font > sz = sz + 1 '''return''' dp[n] Здесь функция <tex>0\mathtt{divide}</tex><(a, b) возвращает нужное(*) округление a /p>||<p style="text-alignb. Приведем её код :center;"> '''int''' <tex>0.571429\mathtt{divide}</tex></p>||<p style="text-align:center;"><tex>('''int''' a, '''int''' b) delta = 0.857143</tex></p>||<p style="text-align:center;"><tex> '''if''' (a '''mod''' b ≠ 0) delta = 1</tex '''if''' ((a ></p0 '''and''' b >|-|0) '''or''' (a <p style="text-align:center;"><tex>0 '''and''' b</tex></p>0)) '''return''' [a / b] + delta '''return''' -[|a|<p style="text-align:center;"><tex>0</tex></p>|b|] Такая реализация будет работать за O(n). ==Динамический convex hull trick== Заметим, что условия на прямые, что <p style="text-align:center;"tex>k[i]</tex>0.326531возрастает/убывает и </tex>x[i]</ptex>||<p style="textубывает/возрастает выглядят достаточно редкими для большинства задач. Пусть в задаче таких ограничений нет. Первый способ борьбы с этой проблемой {{---}} отсортировать входные данные нужным образом, не испортив свойств задачи (пример : задача G c Санкт-alignПетербургских сборов к РОИ 2016[http:center;"><tex>0//neerc.ifmo.489796 <ru/tex><school/pcamp-2016/problems/20160318a.pdf]). Но рассмотрим общий случай. По-прежнему у нас есть выпуклая оболочка прямых, имея которую мы за <tex>||O(\log(n))<p style="text-align:center;"/tex>можем найти <tex>0.571429dp[i]</tex>, но теперь вставку </p>|-|<p style="text-align:center;"tex><tex>ai</tex></p>||<p style="text-align:center;">й прямой в оболочку уже нельзя выполнить описанным ранее способом за <tex>0.326531 O(1)</tex></p>||<p style="textв среднем. У нас есть выпуклая оболочка, наша прямая пересекает ее, возможно, «отсекая» несколько отрезков выпуклой оболочки в середине (рис. 4 : красная прямая {{---align}} та, которую мы хотим вставить в наше множество). Более формально :center;">теперь наша новая прямая будет ниже остальных при <tex>0.419825 x \in [x1; x2]</tex>, где </ptex>||<p style="textx1, x2 \in R</tex> {{---align:center;">}} точки пересечения с некоторыми прямыми, причем <tex>0.466472 x2</tex></p>||<p style="text-align:center;">не обязательно равно <tex>0.489796 + \infty</tex></p>|- [[Файл:picture4convexhull.png]] | Чтобы уметь вставлять прямую в множество будем хранить <p style="text-alignmath>std::center;"set</math>(или любой аналог в других языках программирования) пар <tex>c(k, st)</tex>= </p>||<p style="text-align:center;"tex>(коэффицент прямой, ее номер в глобальной нумерации)</tex>0.326531Когда приходит новая прямая, ищем последнюю прямую с меньшим угловым коэффицентом, чем у той прямой, которую мы хотим добавить в множество. Поиск такой прямой занимает </tex>O(\log(n))</ptex>||<p style=. Начиная с найденной прямой выполняем "text-align:center;старый"алгоритм (удаляем, пока текущая прямая множества бесполезна). И симметричный алгоритм применяем ко всем прямым справа от нашей (удаляем правого соседа нашей прямой, пока она пересекает нас позже, чем своего правого соседа). Асимптотика решения составит <tex>O(\log(n))</tex>0.379842на каждый из </tex>n</ptex>||запросов «добавить прямую» + <p style="texttex>O(n\cdot\log(n))</tex> суммарно на удаление прямых, т.к. по-alignпрежнему каждая прямая не более одного раза удалится из множества, а каждое удаление из std::center;">set занимает <tex>0.406497O(\log(n))</tex>времени. Итого </pmath>||O(n\cdot\log(n))<p style/math>. == Альтернативный подход =="text-align:center;"> Другой способ интерпретировать выражение <tex>dp[i] = \min\limits_{j=0.419825</tex>..i-1}(c[j] \cdot a[i] + dp[j])</ptex>|-|<p style="text-align заключается в том, что мы будем пытаться свести задачу к стандартной выпуклой оболочке множества точек. Перепишем выражение средующим образом :center;"><tex>dp[j] + a[i] \cdot c[j] = (dp[j], c[j]) \cdot (1, a[i])</tex>, т.е. запишем как скалярное произведение векторов </ptex>||<p stylev[j] ="text-align:center;"(dp[j], c[j])</tex>и <tex>0.406497u[i] = (1, a[i])</tex>. Вектора </ptex >||<p stylev[j] ="text-align:center;" (dp[j], c[j])</tex>хотелось бы организовать так, чтобы за <tex>0.414113O(\log(n))</tex>находить вектор, максимизирующий выражение </p>||<p style="text-align:center;"tex>v[j] \cdot u[i]</tex>0.417921 Посмотрим на рис. 5. Заметим интуитивно очевидный факт : красная точка (вектор) </tex>j</p>||<p style="text-align:center;"><tex>0.419825не может давать более оптимальное значение </tex>v[j] \cdot u[i]</p>|-|<p style="text-align:center;"tex>одновременно чем обе синие точки. По этой причине нам достаточно оставить выпуклую оболочку векторов <tex>bv[j]</tex>, а ответ на запрос {{---}} это поиск </p>||<p style="text-align:center;"tex>v[j]</tex>0.406497, максимизирующего проекцию на </tex>u[i]</p>||<p style="text-align:center;"><tex>0.410849Это задача поиска ближайшей точки выпуклого многоугольника (составленного из точек выпуклой оболочки) к заданной прямой (из </tex>(0, 0)</ptex>||в <p style="text-align:center;"tex>(1, a[i])</tex>0).413025 Ее можно решить за </tex>O(\log(n))</ptex>||<p style="textдвумя бинарными или одним тернарным поиском Асимптотика алгоритма по-align:center;">прежнему составит <tex>0.414113O(n \cdot \log(n))</tex></p>|- |<p style="text-align [[Файл:center;"><tex>apicture5convexhull.png]] Докажем то, что описанный выше алгоритм корректен. Для этого достаточно показать, что если имеются </texmath>3</pmath>||вектора <p style="text-align:center;"math>a, b, c<tex/math>0, расположенные как на рис.410849</tex5, т.е. точка <math>b</p>||<p style="text-align:center;"math>не лежит на выпуклой оболочке векторов <tex>0.412093, a, b, c </tex>: </ptex>\Leftrightarrow |[a-b, b-c]|<p style="text-align:center;">0 </tex>0.412714, то либо </tex>(a, u[i])</ptex>||оптимальнее, чем <p style="text-align:center;"tex>(b, u[i])</tex>0.413025, либо <tex>(c, u[i])</tex>оптимальнее, чем <tex>(b, u[i])</ptex>. |}== Оценка длины кодового слова == {{Теорема |statementid=При арифметическом кодировании длина кодового слова не превышает энтропии исходного текстаth12392. ||proofstatement=Введём следующие обозначения: *Если есть <tex>l3</tex> {{---}} длина текста,*вектора <tex>na, b, c</tex> {{---}} размер алфавита,*таких что <tex>f_i</tex> {{|[a-b, b--}} частота встречаемости символа,*c]| <tex>p_i0</tex> {{---}} вероятность вхождения символа. Размер сообщения то либо <texmath>L(a, u) < (b, u)</texmath> можно найти по формуле: , либо <div stylemath>(c, u) < (b, u)</math>, где вектор <math>u ="text-align: center(1;"k)</math>. |proof=По условию теоремы известно, что <tex> L = \prod|[a-b, b-c]| < 0 \limits_Leftrightarrow (a_{i=1x}^l p_- b_{fix} = )\prod\limits_cdot(b_{i=1y}^n p_- c_{iy}^) < (a_{f_y} - b_{iy}) \cdot (b_{x}- c_{x})</tex>(*). Предположим (от противного), что </divtex> Число бит в закодированном тексте: (b, u) <div style="text-align: center;"><tex>(a, u) \log_2 L = Leftrightarrow b_{x} + k \sum\limits_cdot b_{y} < c_{i=1x}^n f_i+ k \cdot c_{y} \log_2 p_i = l \cdot \sum\limits_Leftrightarrow (b_{i=1x}^n p_i\cdot \log_2 p_i = -l c_{x}) < k \cdot H(p_1...p_nc_{y} - b_{y})</tex>и </divtex>(b, u) < (c, u) \Leftrightarrow b_{x} + k \cdot b_{y} < a_{x}+ k \cdot a_{y}\Leftrightarrow (a_{x} - b_{x}) > k \cdot (b_{y} - a_{y})</tex>. == См. также ==* [[Алгоритм_Хаффмана | Алгоритм Хаффмана]] Подставим эти неравенства в (* [[Алгоритмы_LZ77_и_LZ78 | Алгоритмы LZ77 и LZ78]]* [[Энтропия_случайного_источника | Энтропия случайного источника]] ). Получим цепочку неравенств : <tex>k \cdot (a_{y} - b_{y})</tex><tex> \cdot (c_{y} - b_{y}) == Источники информации ==* [http:k</tex><tex> \cdot (b_{y} - a_{y}) \cdot </ru.wikipedia.orgtex><tex>(b_{y} - c_{y})</wiki/%D0%90%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BA%D0%BE%D0%B4%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5 Википедия tex> <tex> < (a_{x} - b_{x})</tex><tex> \cdot (b_{y} - c_{y})</tex><tex> < (a_{y} - b_{---y}} Арифметическое кодирование]* [https:) \cdot <//en.wikipedia.org/wiki/Arithmetic_coding Wikipedia tex><tex>(b_{x} - c_{---x}} Arithmetic coding]* [http:)<//www.sernam.ru/cod_3.php Арифметическое кодирование]* [http://rain.ifmo.ru/cat/view.php/vis/data-compression/arithmetic-coding-2006 Визуализатор арифметического кодирования] [[Категория: Дискретная математика и алгоритмы]][[tex> <tex>< k \cdot (a_{y} - b_{y})</tex><tex> \cdot (c_{y} - b_{y})</tex>. Получили противоречие : <tex>k \cdot (a_{y} - b_{y}) \cdot (c_{y} - b_{y}) < k \cdot (a_{y} - b_{y}) \cdot (c_{y} - b_{y})</tex>. Значит предположение неверно, чтд. }} Из доказанной теоремы и следует корректность алгоритма. ==См. также== 1) http://neerc.ifmo.ru/wiki/index.php?title=Статические_выпуклые_оболочки:_Джарвис,_Грэхем,_Эндрю,_Чен,_QuickHull 2) http://neerc.ifmo.ru/wiki/index.php?title=Динамическое_программирование [[Категория:Дискретная математика и алгоритмы]] [[Категория: Теория вероятностиДинамическое программирование]] [[Категория: Алгоритмы сжатияСпособы оптимизации методов динамического программирования]]