Изменения

Перейти к: навигация, поиск

Арифметическое кодирование

118 252 байта убрано, 21:20, 18 января 2017
Отмена правки 60036 участника 92.255.113.52 (обсуждение)
Convex hull trick '''Арифметическое кодирование''' (англ. ''Arithmetic coding'') {{---}} один алгоритм сжатия информации без потерь, который при кодировании ставит в соответствие тексту вещественное число из методов оптимизации динамического программирования отрезка <tex>[[http:0; 1)<//neerctex>.ifmo.ru/wiki/index.php?title=Динамическое_программирование Данный метод, как и [[Алгоритм Хаффмана|алгоритм Хаффмана]], является [[Энтропия случайного источника|энтропийным]], использующий идею выпуклой оболочкит. Позволяет улучшить ассимптотику решения некоторых задачье. длина кода конкретного символа зависит от частоты встречаемости этого символа в тексте. Арифметическое кодирование показывает более высокие результаты сжатия, решемых методом динамического программирования чем алгоритм Хаффмана, для данных с неравномерными распределениями вероятностей кодируемых символов. Кроме того, при арифметическом кодировании каждый символ кодируется нецелым числом бит, что эффективнее кода Хаффмана (теоретически, символу <mathtex>Oa</tex> с вероятностью появления <tex>p(n^2a)</mathtex> до допустимо ставить в соответствие код длины <tex>O(n-\cdot\loglog_2 p(n)a)</tex>, следовательно, при кодировании алгоритмом Хаффмана это достигается только с вероятностями, равными обратным степеням двойки). Техника впервые появилась в 1995 году (задачу на нее предложили в USACO {{---}} национальной олимпиаде США по программированию). Массовую известность получила после IOI (международной олимпиады по программированию для школьников) 2002. == Принцип действия =====Кодирование =Пример задачи, решаемой методом convex hull trick== На вход алгоритму передаются текст для кодирования и список частот встречаемости символов.# Рассмотрим задачу на ДП: {{Задача |definition = Есть отрезок <mathtex>n<[0; 1)</math> деревьев с высотами <tex>a_1на координатной прямой. # Поставим каждому символу текста в соответствие отрезок, a_2длина которого равна частоте его появления.# Считаем символ из входного потока и рассмотрим отрезок, \dotsсоответствующий этому символу. Разделим этот отрезок на части, a_n</tex> пропорциональные частотам встречаемости символов.# Повторим пункт (в метрах3)до конца входного потока. Требуется спилить их все, потратив минимальное количество монет на заправку бензопилы. Но пила устроена так# Выберем любое число из получившегося отрезка, что она может спиливать только по 1 метру от дерева, к которому ее примениликоторое и будет результатом арифметического кодирования. Также после срубленного метра (любого дерева) пилу нужно заправлять, платя за бензин определенной кол-во монет. Причем стоимость бензина зависит от срубленных (полностью) деревьев. Если сейчас максимальный индекс срубленного дерева равен === Псевдокод === *<texmath>i\mathtt{s}\,</texmath>{{---}} текст, то цена заправкиподаваемый на вход; равна *<texmath>c_i\mathtt{n}\,</texmath>. Изначально пила заправлена.{{---}} длина исходного текста; Также известны следующие ограничения : *<texmath>c_n = 0\mathtt{m}\, a_1 = 1, a_i</texmath> возрастают, {{---}} мощность алфавита исходного текста;*<texmath>c_i\mathtt{letters[m]}\,</texmath> убывают. Изначально пила заправлена. (убывание и возрастание нестрогие) }{{---}}массив символов, составляющих алфавит исходного текста; (Задача H с Санкт-Петербургских сборов к РОИ 2016*<math>\mathtt{probability[http://neerc.ifmo.ru/school/camp-2016/problems/20160318a.pdfm]) }\,</noinclude> <includeonlymath>{{#if: ---}} массив вероятностей обнаружения символа в тексте; *<math>\mathtt{Segment}\,</math> {{neat|---}}}| структура, задающая подотрезок отрезка <div style="background-color: #fcfcfctex>[0; float:left;"1)</tex>, соответствующего конкретному символу на основе частотного анализа. Имеет поля: **<div style="background-color: #ddd;"math>'''Задача:'''\mathtt{left}\,</divmath> <div style="border:1px dashed #2f6fab; padding: 8px; font{{---style: italic}} левая граница подотрезка;"**<math>\mathtt{right}\,</math> {{definition---}}}</div>правая граница подотрезка; *</divmath>| \mathtt{left}\,<table border="0" width="100%"/math> , <trmath>\mathtt{right}\,<td style="background/math> {{---color: #ddd">'}} границы отрезка, содержащего возможный результат арифметического кодирования. <code> '''struct'''ЗадачаSegment: '''</td></tr>double''' left '''double''' right  <tr><td style="border:1px dashed #2f6fab; padding '''Segment'''[m] defineSegments(letters: 8px; background-color'''char'''[m], probability: #fcfcfc; font-style'''double'''[m]): italic;">{{{definition}}}</td></tr> </table>}} </includeonly> ==Наивное решение= '''Segment'''[m] segment '''double''' l =0 Сначала заметим важный факт : т '''for''' i = 0 '''to''' m - 1 segment[letters[i]].к. <tex>cleft = l segment[letters[i]</tex> убывают (нестрого) и <tex>c[n] .right = 0</tex>, то все <tex>cl + probability[i] l = segment[letters[i]</tex> неотрицательны].right Понятно, что нужно затратив минимальную стоимость срубить последнее '''return''' segment  '''double''' arithmeticCoding(<tex>n</tex>-е) деревоletters: '''char'''[m], т.к. после него все деревья можно будет рубить бесплатно (т.к. <tex>cprobability: '''double'''[nm], s: '''char'''[n] = 0</tex>). Посчитаем следующую динамику : <tex>dp '''Segment'''[im]</tex> {{---}} минимальная стоимостьsegment = defineSegments(letters, заплатив которую можно добиться того, что дерево номер <tex>i.</tex> будет срублено.probability) '''double''' left = 0 База динамики : <tex>dp[ '''double''' right = 1] '''for''' i = 0</tex>, т.к. изначально пила заправлена и высота первого дерева равна '''to''' n - 1, по условию задачи. Переход динамики : понятно, что выгодно рубить сначала более дорогие и низкие деревья, а потом более высокие и дешевые (док '''char''' symb = s[i] '''double''' newRight = left + (right -во этого факта оставляется читателям как несложное упражнение, тleft) * segment[symb].к. эта идея относится скорее к теме жадных алгоритмнов, чем к теме данной статьиright '''double''' newLeft = left + (right - left)* segment[symb]. Поэтому перед <tex>ileft left = newLeft right = newRight '''return''' (left + right) / 2</texcode>-м деревом мы обязательно срубили какое-то  '''Замечание:''' для оптимизации размера кода можно выбрать из полученного на последнем шаге диапазона <tex>j[left; right]</tex>-е, причем число, содержащее наименьшее количество знаков в двоичной записи. === Декодирование ===Алгоритм по вещественному числу восстанавливает исходный текст.# Выберем на отрезке <tex>j \leqslant i - [0; 1)</tex>, разделенном на части, длины которых равны вероятностям появления символов в тексте, подотрезок, содержащий входное вещественное число. Поэтому чтобы найти <tex>dp[i]</tex> нужно перебрать все <tex>Символ, соответствующий этому подотрезку, дописываем в ответ.# Нормируем подотрезок и вещественное число.# Повторим пункты 1 \leqslant j \leqslant i {{--- 1</tex> и попытаться использовать }}2 до тех пор, пока не получим ответ для дерева намер . === Псевдокод === *<texmath>j</tex>. Итак\mathtt{code}\, пусть перед <tex>i</texmath>{{-м деревом мы полностью срубили --}} вещественное число, подаваемое на вход;*<texmath>j\mathtt{n}\,</texmath>{{---е, причем высота }} длина восстанавливаемого текста;*<texmath>i\mathtt{m}\,</texmath>{{-го дерева составляет --}} мощность алфавита исходного текста;*<texmath>a\mathtt{letters[im]}\,</texmath>{{---}} массив символов, а т.к. последнее дерево, которое мы срубили имеет индекс составляющих алфавит исходного текста;*<texmath>j\mathtt{probability[m]}\,</texmath>, то стоимость каждого метра {{---}} массив вероятностей обнаружения символа в тексте; *<texmath>i\mathtt{segment}\,</texmath>{{---го дерева составит }} структура, задающая подотрезок отрезка <tex>c[j]0; 1)</tex>, соответствующего конкретному символу на основе частотного анализа. Поэтому на сруб Имеет поля:** <texmath>i\mathtt{left}\,</texmath>{{--го дерева мы потратим -}} левая граница подотрезка;** <texmath>a[i] \cdot c[j]mathtt{right}\,</texmath> монет. Также не стоит забывать, ситуацию, когда {{---}} правая граница подотрезка;** <texmath>j\mathtt{character}\,</texmath>{{-е дерево полностью срублено, мы получили не бесплатно, а за --}} значение символа. <texcode>dp '''struct''' Segment: '''double''' left '''double''' right '''char''' character  '''Segment'''[jm]</tex> монет. Итогвая формула пересчета : <tex>dpdefineSegments(letters: '''char'''[in] = \min\limits_{j=1...i-1} (dp, probability: '''double'''[j] + a[i] \cdot c[jn])</tex>. Посмотрим на код выше описанного решения: '''intSegment''' <tex>\mathtt{simpleDP}</tex>([m] segment '''intdouble''' a[n], l = 0 '''for''' i = 0 '''intto''' cm - 1 segment[ni]).left = l dp segment[1i] .right = 0 dpl + probability[2i] = dp segment[3i] = ... character = dpletters[ni] l = <tex>\infty</tex>segment[i].right '''forreturn''' i = 1..n-1segment dp[i] = <tex>+\infty</tex> '''forstring'''arithmeticDecoding(letters: '' j = 0..i-1 'char'''[m], probability: '''ifdouble''' (dp[j] + a[i] <tex>\cdot</tex> c[j] < dp[im], code: '''double''', n: '''int'''): dp[i] = dp[j] + a[i] <tex>\cdot</tex> c[j] '''returnSegment''' dp[nm] Нетрудно видетьsegment = defineSegments(letters, что такая динамика работает за <tex>O(n^2probability)</tex>. '''string''' s = "" ==Ключевая идея оптимизации= '''for''' i = Для начала сделаем замену обозначений. Давайте обозначим <tex0 '''to''' n - 1 '''for''' j = 0 '''to''' m - 1 '''if''' code >dp= segment[j].left '''and''' code </tex> за <tex>bsegment[j]</tex>, <tex>a.right s += segment[ij]</tex> за <tex>x.character code = (code – segment[ij]<.left) /tex>, а <tex>c(segment[j]</tex> за <tex>k.right – segment[j].left) '''break''' '''return''' s</texcode>. Теперь формула приняла вид <tex>dp[i] = \min\limits_{j=0'''Замечание:''' кодировщику и декодировщику должно быть известно, когда завершать работу.Для этого можно передавать в качестве аргумента длину текста или символ конца файла, после которого процесс должен быть остановлен..i-1}(k[j] \cdot x[i] + b[j]) '''Замечание:''' Несмотря на преимущества арифметического кодирования, существует проблема при его практическом применении из-за несовершенства представления чисел с плавающей точкой в памяти компьютера {{---}} поскольку некоторые дробные числа не могут быть точно представлены в двоичном коде, используемом современными процессорами (например, </tex>. Выражение <tex>k[j] \cdot x + b[j]</tex> \dfrac{1}{{---3}} это в точности уравнение прямой вида <tex>y = kx + b</tex>), границы символов будут округлены, что может повлечь за собой неверную работу алгоритма при больших объёмах данных. Сопоставим каждому В общем случае, алгоритм можно модифицировать так, чтобы результатом было дробное число. В такой реализации вероятность встречи символа представляется в виде рационального числа. Поскольку в каждой итерации будет переход из текущего отрезка в один из его <tex>jm</tex>подотрезков, обработанному ранее, прямую кратных по длине <tex>y[j](x) = k[j] \cdot x + b[j]n</tex>. Из условия «, а всего итераций <tex>c[i]n</tex> убывают , в конечном результате знаменатель дроби не превысит <tex>\Leftrightarrow k[j]n^{n}</tex> уменьшаются с номером , а поскольку сумма всех вероятностей встречи символов равна <tex>j1</tex>» следует то, что прямые, полученные ранее отсортированы полученная дробь будет находиться в порядке убывания углового коэффициентпромежутке <tex>[0; 1)</tex>. Давайте нарисуем несколько таких прямых : [[Файл:picture1convexhull.png]]== Пример работы == Выделим множество точек Рассмотрим в качестве примера строку <tex>(x0, y0)abacaba</tex> , таких что все они принадлежат одной из прямых и при этом нету ни одной прямой <tex>y’(x)</tex:=== Кодирование ==={|class="wikitable"!Символ||Частота появления|-|<p style="text-align:center;">, такой что <tex>y’(x0) < y0a</tex>. Иными словами возьмем «выпуклую (вверх) оболочку» нашего множества прямых (её еще называют нижней ошибающей множества прямых на плоскости). Назовем ее «<tex/p>y ||<p style= convex(x)</"text-align:center;"><tex>»0. Видно, что множество точек 571429<math/tex>(x, convex(x))</mathp> представляет собой выпуклую вверх функцию. |- |<p style==Цель нижней огибающей множества прямых== Пусть мы считаем динамику для "text-align:center;"><tex>ib</tex>-го дерева. Его задает <tex/p>||<p style="text-align:center;">x[i]</tex>0. Итак, нам нужно для данного 285714</tex>x[i]</p>|-|<p style="text-align:center;"><tex> найти c</tex>\min\limits_{j</p>||<p style="text-align:center;"><tex>0..i-1142857</tex></p>|}(k[j] \cdot x[i] + b[iФайл:Code_png.png|thumb|right|200px|Пример работы кодировщика ]]) = \min\limits_{j|class=0..i"wikitable"!Считанный символ||Левая граница отрезка||Правая граница отрезка|-1}(y[j](x[i]))|||<p style="text-align:center;"></tex>. Это выражение есть 0<math/tex>convex(x[i])</mathp>. Из монотонности угловых коэффицентов отрезков, задающих выпуклую оболочку, и их расположения по координаты x следует то, что отрезок, который пересекает прямую ||<tex>x p style= x[i]"text-align:center;"></tex>, можно найти бинарным поиском. Это потребует 1</tex>O(\log(n))</texp>|-|<p style="text-align:center;"> времени на поиск такого <tex>ja</tex>, что <tex/p>dp[i] ||<p style= k[j] \cdot x[i] + b[j]"text-align:center;"></tex>. Теперь осталось научиться поддерживать множество прямых и быстро добавлять 0</tex>i</texp>-ю прямую после того, как мы посчитали ||<texp style="text-align:center;">b[i] = dp[i]</tex>0. Воспользуемся идеей алгоритма построения выпуклой оболочки множества точек. Заведем 2 стека 571429</tex>k[]</texp>|-|<p style="text-align:center;"> и <tex>b[]</tex>, которые задают прямые в отсортированном порядке их угловыми коэффицентами и свободными членами</p>||<p style="text-align:center;"><tex>0. Рассмотрим ситуацию, когда мы хотим добавить новую (326531</tex>i</texp>||<p style="text-тую) прямую в множество. Пусть сейчас в множестве лежит align:center;"><tex>sz0.489796</tex> прямых (нумерация с 1). Пусть <tex>(xL, yL)<</texp> {{|-|<p style="text--}} точка пересечения align:center;"><tex>sz - 1a</tex>-й прямой множества и <tex>sz</texp>||<p style="text-align:center;">-й, а <tex>(xR, yR)0.326531</tex> {{</p>||<p style="text---}} точка пересечения новой прямой, которую мы хотим добавить в конец множества и align:center;"><tex>sz0.419825</tex>-й. Нас будут интересовать только их <tex/p>|-|<p style="text-align:center;">x</tex>-овые координаты c</tex>xL</texp> и ||<texp style="text-align:center;">xR</tex>, соответственно0. Если оказалось, что новая прямая пересекает 406497</tex>sz</texp>-ю прямую выпуклой оболочки позже, чем ||<texp style="text-align:center;">sz</tex>0.419825</tex>sz - 1</texp>|-ю, т.е. |<texp style="text-align:center;">(xL \geqslant xR)</tex>, то a</tex>sz</texp>||<p style="text-ю удалим из нашего множества, иначе {{---}} остановимся. Так будем делать, пока либо кол-во прямых в стеке не станет равным 2, либо <texalign:center;">xL</tex> не станет меньше <tex>xR0.406497</tex> Асимптотика : аналогично обычному алгоритму построения выпуклой оболочки, каждая прямая ровно <math/p>1||</mathp style="text-align:center;"> раз добавится в стек и максимум <mathtex>10.414113</math> раз удалится. Значит время работы перестройки выпуклой оболочки займет <tex>O(n)</texp> суммарно. |- [[Файл|<p style="text-align:picture2convexhull.png]] [[Файл:picture3convexhull.png]] {{Теорема center;"><tex>b</tex></p>|id=th1239. |statement<p style=Алгоритм построения нижней огибающей множества прямых корректен. |proof=Достаточно показать, что последнюю прямую нужно удалить из множества т.и т.т., когда она наша новая прямая пересекает ее в точке с координатой по оси X, меньшей, чем последняя {{---}} предпоследнюю. Пусть "text-align:center;"><tex>Y(x) = Kx + B0.410849</tex> {{</p>||<p style="text---}} уравнение новой прямой, align:center;"><tex>y[i](x) = K[i]x + B[i]0.413025</tex> {{-</p>|-|<p style="text-}} уравнения прямых множества. Тогда т.к. align:center;"><tex>K < K[sz]a</tex>, то при <tex/p>x \in [||<p style="text- \inftyalign:center; xR] : y[sz](x) "><= Y(x)tex>0.410849</tex>, а т.к. <tex/p> K[sz] ||< K[sz p style="text- 1]align:center;"><tex>0.412093</tex>, то при </p>|}Код: <tex>x \in [xL; + \infty] : y[sz - 1](x) \geqslant y[sz](x)0.411471</tex>. Если  === Декодирование ===Код: <tex>xL < xR0.411471</tex>, то при <tex>x \in [[xL; xR] Файл: y[sz - 1decode1_png.png|thumb|right|200px|Пример работы декодировщика ] \geqslant y[sz](x) и Y(x) \geqslant y[sz](x){|class="wikitable"!Декодируемый символ||Код|-|</texp style="text-align:center;">, т.е. на отрезке <tex>[xL; xR]a</tex> прямая номер sz лежит ниже остальных и её нужно оставить в множестве. Если же <tex/p>xL ||<p style="text-align:center;"> xR</tex>, то она ниже всех на отрезке 0.411471</tex>[xL; xR] = \varnothing </tex</p>, т.е. её можно удалить из множества }}|- |<p style==Детали реализации:== Будем хранить 2 массива "text-align: center;"><tex>front[]b</tex> {{---}} <tex/p>x||</texp style="text-align:center;">-координаты, начиная с которых прямые совпадают с выпуклой оболочкой (т.е. i-я прямая совпадает с выпуклой оболочкой текущего множества прямых при <tex>x0.720074</tex> <tex>\in</texp> |-|<tex>[front[i]p style="text-align:center; front[i + 1])</tex"> ) и <tex>st[]a</tex> {{---}} номера деревьев, соответствующих прямым (т.е. <tex/p>i||</texp style="text-align:center;">-я прямая множества, где <tex>i0.520259</tex> <tex/p>\in|-|</texp style="text-align:center;"> <tex>[1; sz]c</tex> соответствует дереву номер <tex/p>sz[i]||</texp style="text-align:center;">). Также воспользуемся тем, что <tex>x[i] = a[i]0.910454</tex> возрастают (по условию задачи), а значит мы можем искать первое такое <tex>j</texp>, что |-|<tex>x[i] \geqslant front[j]p style="text-align:center;"><tex>a</tex> не бинарным поиском, а методом двух указателей за </p>||<p style="text-align:center;"><tex>O(n)0.373178</tex> операций суммарно. Также массив front[] можно хранить в целых числах, округляя х</p>|-координаты в сторону лежащих правее по оси x до ближайшего целого (*), т.к. на самом деле мы, считая динамику, подставляем в уравнения прямых только целые |<texp style="text-align:center;">x[i]</tex>, а значит если b</tex>k</texp>||<p style="text-align:center;">-я прямая пересекается с <tex>k+10.653061</tex>-й в точке <tex/p>z +|-|</texp style="text-align:center;"> <tex>\alphaa</tex> (<math/p>z||</math>p style="text-целое, <texalign:center;">\alpha</tex> <tex>\in</tex> <tex>[0;1).285714</tex>), то мы будем подставлять в их уравнения <tex>z</tex> или <tex>z + 1</tex>. Поэтому можно считать, что новая прямая начинает совпадать с выпуклой оболочкой, начиная с <tex>x = z+1</texp> |}  ==Реализация== '''intЗамечание:''' <tex>\mathtt{ConvexHullTrick}при декодировании текста можно не только нормализовывать рабочий отрезок и текущий код, но и уменьшать рабочий отрезок (аналогично кодированию), не изменяя значение кода.=== Декодирование (второй способ)===Код: <tex>0.411471</tex>('''int''' a[n[Файл:decode2_png.png|thumb|right|200px|Пример работы декодировщика (второй способ) ], '''int''' c[n]) st[1] {|class= 1"wikitable" from[1] !Декодируемый символ||colspan= -"4" |Границы отрезка|-|<p style="text-align:center;"><tex>\inftya</tex><font color/p>||<p style=green"text-align:center;">// первая прямая покрывает все x-ы, начиная с -∞ <tex>0</fonttex> sz = 1 <font color/p>||<p style=green"text-align:center;"><tex>0.571429<// текущий размер выпуклой оболочки tex></fontp> pos ||<p style= 1 "text-align:center;"><font color=greentex>0.857143</tex></ текущая позиция первого такого j, что x[i] \geqslant front[st[j]] p>||<p style="text-align:center;"><tex>1</tex></font p> '''for''' i |-|<p style= 2..n '''while''' (front[pos] "text-align:center;">< x[i]) tex>b<font color=green/tex><// метод 1 указателя (ищем первое pos, такое что x[i] покрывается p>||<p style="областью действияtext-align:center;" st[pos]-той прямой ></font tex> pos = pos + 1 j = st[pos] dp[i] = K[j]0<math/tex>\cdot</mathp>a[i] + B[j] '''if''' (i ||< n) p style="text-align:center;"><font color=greentex>0.326531<// если у нас добавляется НЕ последняя прямая, то придется пересчитать выпуклую оболочку tex></font p> K[i] ||<p style= c[i] "text-align:center;"><font color=greentex>0.489796 <// наши переобозначения переменных tex></font p> B[i] = dp[i] ||<font colorp style=green"text-align:center;">// наши переобозначения переменных </font tex>0.571429</tex></p>|- x |<p style= "text-align:center;"><tex>\inftya</tex> '''while''' ''true'' j </p>||<p style= st[sz] x = divide(B[j] "text- B[i], K[i] - K[j]) align:center;"><font color=greentex>0.326531 <// x-координата пересечения с последней прямой оболочки, округленное в нужную сторону (*) tex></font p> '''if''' (x > from[sz]) '''break''' ||<font colorp style=green>// перестаем удалять последнюю прямую из множества, если новая прямая пересекает ее позже, чем начинается ее "область действияtext-align:center;" ></font tex> sz = sz - 10.419825 <font color=green/tex>// удаляем последнюю прямую, если она лишняя </font p> st[sz + 1] ||<p style= i from[sz + 1] = x "text-align:center;"><font color=greentex>0.466472 <// добавили новую прямую tex></font p> sz ||<p style= sz + 1 '''return''' dp[n] Здесь функция "text-align:center;"><tex>\mathtt{divide}0.489796 </tex>(a, b) возвращает нужное(*) округление a </ b. Приведем её код :p>|- '''int''' |<p style="text-align:center;"><tex>\mathtt{divide}c</tex>('''int''' a, '''int''' b) delta </p>||<p style="text-align:center;"><tex>0.326531</tex></p>||<p style= "text-align:center;"><tex>0 '''if''' (a '''mod''' b ≠ 0) delta .379842</tex></p>||<p style= 1 '''if''' ((a "text-align:center;"><tex> 0 '''and''' b .406497</tex> 0) '''or''' (a < 0 '''and''' b /p>||< p style="text-align:center;"><tex>0)) '''return''' [a .419825</tex></ b] + deltap> '''return''' |-[|a| / |b|] Такая реализация будет работать за O(n). ==Динамический convex hull trick== Заметим, что условия на прямые, что <p style="text-align:center;"><tex>k[i]a</tex> возрастает</убывает и <texp>x[i]||</tex> убывает/возрастает выглядят достаточно редкими для большинства задач. Пусть в задаче таких ограничений нет. Первый способ борьбы с этой проблемой {{--p style="text-}} отсортировать входные данные нужным образом, не испортив свойств задачи (пример align: задача G c Санкт-Петербургских сборов к РОИ 2016[http://neerccenter;"><tex>0.ifmo.ru406497</schooltex></camp-2016/problems/20160318a.pdf]). Но рассмотрим общий случай. Поp>||<p style="text-прежнему у нас есть выпуклая оболочка прямых, имея которую мы за align:center;"><tex>O(\log(n))0.414113</tex> можем найти <tex>dp[i]</texp>||<p style="text-align:center;">, но теперь вставку <tex>i0.417921 </tex></p>||<p style="text-й прямой в оболочку уже нельзя выполнить описанным ранее способом за align:center;"><tex>O(1)0.419825</tex> в среднем. У нас есть выпуклая оболочка, наша прямая пересекает ее, возможно, «отсекая» несколько отрезков выпуклой оболочки в середине (рис. 4 : красная прямая {{--</p>|-|<p style="text-}} та, которую мы хотим вставить в наше множество). Более формально align: теперь наша новая прямая будет ниже остальных при center;"><tex>x \in [x1; x2]b</tex>, где <tex/p>x1, x2 \in R||</tex> {{p style="text---}} точки пересечения с некоторыми прямыми, причем align:center;"><tex>x20.406497</tex> не обязательно равно </p>||<p style="text-align:center;"><tex>+ \infty0.410849</tex> [[Файл</p>||<p style="text-align:picture4convexhull.png]] Чтобы уметь вставлять прямую в множество будем хранить center;"><mathtex>std::set0.413025</math> (или любой аналог в других языках программирования) пар <tex>(k, st)</texp> ||<p style= "text-align:center;"><tex>(коэффицент прямой, ее номер в глобальной нумерации)0.414113</tex>. Когда приходит новая прямая, ищем последнюю прямую с меньшим угловым коэффицентом, чем у той прямой, которую мы хотим добавить в множество. Поиск такой прямой занимает </p>|-|<p style="text-align:center;"><tex>O(\log(n))a</tex>. Начиная с найденной прямой выполняем </p>||<p style="старыйtext-align:center;" алгоритм (удаляем, пока текущая прямая множества бесполезна). И симметричный алгоритм применяем ко всем прямым справа от нашей (удаляем правого соседа нашей прямой, пока она пересекает нас позже, чем своего правого соседа). Асимптотика решения составит ><tex>O(\log(n))0.410849</tex> на каждый из </p>||<p style="text-align:center;"><tex>n0.412093</tex> запросов «добавить прямую» + </p>||<p style="text-align:center;"><tex>O(n\cdot\log(n))0.412714</tex> суммарно на удаление прямых, т.к. по</p>||<p style="text-прежнему каждая прямая не более одного раза удалится из множества, а каждое удаление из stdalign::set занимает center;"><tex>O(\log(n))0.413025</tex> времени. Итого <math/p>O(n\cdot\log(n))</math>. |}== Альтернативный подход Оценка длины кодового слова == Другой способ интерпретировать выражение <tex>dp[i] = \min\limits_{j{Теорема |statement=0При арифметическом кодировании длина кодового слова не превышает энтропии исходного текста...i-1}(c[j] \cdot a[i] + dp[j])  ||proof=Введём следующие обозначения: *<tex>l</tex> заключается в том{{---}} длина текста, что мы будем пытаться свести задачу к стандартной выпуклой оболочке множества точек. Перепишем выражение средующим образом : *<tex>dp[j] + a[i] \cdot c[j] = (dp[j], c[j]) \cdot (1, a[i])</n</tex>{{---}} размер алфавита, т.е. запишем как скалярное произведение векторов *<tex>f_i</tex>v[j] = (dp[j]{{---}} частота встречаемости символа, c[j])*</tex> и p_i</tex >u[i] = (1, a[i])</tex >{{---}} вероятность вхождения символа. Вектора  Размер сообщения <tex >v[j] = (dp[j], c[j])L</tex> хотелось бы организовать так, чтобы за можно найти по формуле: <tex >O(\log(n))</texdiv style="text-align: center;"> находить вектор, максимизирующий выражение <tex>v[j] L = \prod\cdot u[limits_{i]</tex>. Посмотрим на рис. 5. Заметим интуитивно очевидный факт : красная точка (вектор) =1}^l p_{fi} = \prod\limits_{i=1}^n p_{i}^{f_{i}}</tex>j</texdiv> не может давать более оптимальное значение  Число бит в закодированном тексте: <tex>v[j] \cdot u[i]</texdiv style="text-align: center;"> одновременно чем обе синие точки. По этой причине нам достаточно оставить выпуклую оболочку векторов <tex>v[j]</tex>, а ответ на запрос \log_2 L = \sum\limits_{{---i=1}} это поиск ^n f_i\cdot \log_2 p_i = l \cdot \sum\limits_{i=1}^n p_i\cdot \log_2 p_i = -l \cdot H(p_1...p_n)</tex>v[j]</texdiv>, максимизирующего проекцию на <tex>u}} == См. также ==* [[Алгоритм_Хаффмана | Алгоритм Хаффмана]]* [[iАлгоритмы_LZ77_и_LZ78 | Алгоритмы LZ77 и LZ78]]</tex>. Это задача поиска ближайшей точки выпуклого многоугольника (составленного из точек выпуклой оболочки) к заданной прямой (из <tex>(0, 0)<* [[Энтропия_случайного_источника | Энтропия случайного источника]] == Источники информации ==* [http:/tex> в <tex>(1, a[i])</tex>)ru.wikipedia. Ее можно решить за <tex>O(\log(n))<org/wiki/tex> двумя бинарными или одним тернарным поиском Асимптотика алгоритма по-прежнему составит <tex>O(n \cdot \log(n))</tex> [[Файл:picture5convexhull.png]] Докажем то, что описанный выше алгоритм корректен%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 Википедия {{---}} Арифметическое кодирование]* [https://en.wikipedia. Для этого достаточно показать, что если имеются <math>3<org/wiki/Arithmetic_coding Wikipedia {{---}} Arithmetic coding]* [http:/math> вектора <math>a, b, c</math>, расположенные как на рисwww. 5, тsernam.еru/cod_3. точка <math>b<php Арифметическое кодирование]* [http://rain.ifmo.ru/cat/view.php/math> не лежит на выпуклой оболочке векторов <tex>0, a, b, c <vis/data-compression/tex> : <tex> \Leftrightarrow |[aarithmetic-b, bcoding-c2006 Визуализатор арифметического кодирования]| < 0 </tex>, то либо <tex>(a, u[i])</tex> оптимальнее, чем <tex>(b, u[i])</tex>, либо <tex>(c, u[i])</tex> оптимальнее, чем <tex>(b, u[i])</tex>. {{Теорема |id=th12392. |statement=Если есть <tex>3</tex> вектора <tex>a, b, c</tex>, таких что <tex>|[a-b, b-c]| < 0</tex> то либо <math>(a, u) < (b, u)</math>, либо <math>(c, u) < (b, u)</math>, где вектор <math>u = (1; k)</math>. |proof=По условию теоремы известно, что <tex>|[a-b, b-c]| < 0 \Leftrightarrow (a_{x} - b_{x})\cdot(b_{y} - c_{y}) < (a_{y} - b_{y}) \cdot (b_{x} - c_{x})</tex> (*). Предположим (от противного), что <tex>(b, u) < (a, u) \Leftrightarrow b_{x} + k \cdot b_{y} < c_{x} + k \cdot c_{y} \Leftrightarrow (b_{x} - c_{x}) < k \cdot (c_{y} - b_{y})</tex> и <tex>(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>. Подставим эти неравенства в (*). Получим цепочку неравенств : <tex>k \cdot (a_{y} - b_{y})</tex><tex> \cdot (c_{y} - b_{y}) = k</tex><tex> \cdot (b_{y} - a_{y}) \cdot </tex><tex>(b_{y} - c_{y})</tex> <tex> < (a_{x} - b_{x})</tex><tex> \cdot (b_{y} - c_{y})</tex><tex> < (a_{y} - b_{y}) \cdot </tex><tex>(b_{x} - c_{x})</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=Динамическое_программирование [[Категория:Дискретная математика и алгоритмы]] [[Категория: Динамическое программированиеТеория вероятности]] [[Категория: Способы оптимизации методов динамического программированияАлгоритмы сжатия]]
186
правок

Навигация