<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dans</id>
		<title>Викиконспекты - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dans"/>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/Dans"/>
		<updated>2026-04-09T09:22:37Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%A2%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0_%D0%B8%D0%BD%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D0%B9&amp;diff=32824</id>
		<title>Таблица инверсий</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A2%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0_%D0%B8%D0%BD%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D0%B9&amp;diff=32824"/>
				<updated>2013-09-17T12:55:55Z</updated>
		
		<summary type="html">&lt;p&gt;Dans: Необходимо сравнивать со значением в левом поддереве&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Пусть &amp;lt;tex&amp;gt; P = (p_1,p_2,\dots,p_n)&amp;lt;/tex&amp;gt; является [[Действие перестановки на набор из элементов, представление в виде циклов|перестановкой]] чисел &amp;lt;tex&amp;gt; 1, 2,\dots, n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = &lt;br /&gt;
'''Инверсией''' в перестановке &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; называется всякая пара индексов &amp;lt;tex&amp;gt;i, j&amp;lt;/tex&amp;gt; такая, что &amp;lt;tex&amp;gt;1\leqslant i&amp;lt;j\leqslant n&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;P[i]&amp;gt;P[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = &lt;br /&gt;
'''Таблицей инверсий''' перестановки &amp;lt;tex&amp;gt; P &amp;lt;/tex&amp;gt; называют такую последовательность &amp;lt;tex&amp;gt; T = (t_1,t_2,\dots,t_n)&amp;lt;/tex&amp;gt;, в которой &amp;lt;tex&amp;gt;t_i&amp;lt;/tex&amp;gt; равно числу элементов перестановки &amp;lt;tex&amp;gt; P &amp;lt;/tex&amp;gt;, стоящих в &amp;lt;tex&amp;gt; P &amp;lt;/tex&amp;gt; левее числа &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; и больших &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Алгоритм построения ==&lt;br /&gt;
 &lt;br /&gt;
Таблицу инверсий тривиально построить по определению. Для каждого элемента перестановки считаем количество элементов, больших данного и стоящих в перестановке левее него.&lt;br /&gt;
Алгоритм построения в псевдокоде выглядит так: &lt;br /&gt;
 T[1..n] = 0&lt;br /&gt;
 For i = 1..n&lt;br /&gt;
   For j = 1..(i - 1)&lt;br /&gt;
     if P[j] &amp;gt; P[i]&lt;br /&gt;
       T[P[i]] = T[P[i]] + 1&lt;br /&gt;
Сложность данного алгоритма {{---}} &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;. Уменьшить время работы можно используя алгоритм, похожий на сортировку слиянием. &lt;br /&gt;
&lt;br /&gt;
Пусть дано разбиение перестановки на два списка, причём для каждого элемента дано число инверсий слева с элементами того же списка и известно, что все числа первого списка стоят левее всех чисел второго списка в исходной перестановке. Будем считать количество инверсий слева элементов обоих списков следующим образом: сливаем списки, аналогично сортировке слиянием.&lt;br /&gt;
&lt;br /&gt;
Если в результат нужно записать элемент первого списка, то все нерассмотренные элементы второго списка больше, следовательно, количество инверсий для этого элемента не меняется. Если в результат нужно записать элемент второго списка, то все нерассмотренные элементы первого списка больше его и стоят левее. Следовательно, количество инверсий для этого элемента следует увеличить на количетво нерассмотренных элементов второго списка.&lt;br /&gt;
&lt;br /&gt;
Описанный алгоритм записывается в псевдокод следующим образом:&lt;br /&gt;
&lt;br /&gt;
 def inverses_merge(ls1, ls2):&lt;br /&gt;
   result = []&lt;br /&gt;
   it1, it2 = 0, 0&lt;br /&gt;
   while (it1 &amp;lt; len(ls1)) and (it2 &amp;lt; len(ls2)):&lt;br /&gt;
     if ls1[it1].item &amp;lt; ls2[it2].item:&lt;br /&gt;
       result.append(ls1[it1])&lt;br /&gt;
       it1 += 1&lt;br /&gt;
     else:&lt;br /&gt;
       result.append(item = ls2[it2].item, inverses = ls2[it2].inverses + len(ls1) - it1)&lt;br /&gt;
       it2 += 1&lt;br /&gt;
   while (it1 &amp;lt; len(ls1)):&lt;br /&gt;
     result.append(ls1[it1])&lt;br /&gt;
     it1 += 1&lt;br /&gt;
   while (it2 &amp;lt; len(ls2)):&lt;br /&gt;
     result.append(ls2[it2])&lt;br /&gt;
     it2 += 1&lt;br /&gt;
   return result&lt;br /&gt;
 &lt;br /&gt;
 def inverses_get(ls):&lt;br /&gt;
   if len(ls) == 1:&lt;br /&gt;
     return [(item = ls[0], inverses = 0)]&lt;br /&gt;
   else:&lt;br /&gt;
     return inverses_merge(inverses_get(ls.first_half), inverses_get(ls.second_half))&lt;br /&gt;
&lt;br /&gt;
* inverses_merge — процедура, сливающая два списка пар&lt;br /&gt;
* inverses_get — процедура, рекурсивно получающая таблицу инверсий для перестановки&lt;br /&gt;
&lt;br /&gt;
Сложность представленного алгоритма есть &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt;. Алгоритм с такой же сложностью можно построить с помощью дерева отрезков.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм восстановления ==&lt;br /&gt;
&lt;br /&gt;
Для восстановления перестановки по таблицы инверсий &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; воспользуемся следующим соображением: единица стоит на &amp;lt;tex&amp;gt;T_i&amp;lt;/tex&amp;gt;-ом месте (индексируем элементы с нуля), так как остальные числа в перестановке больше единицы. Далее, если известны расположения всех чисел &amp;lt;tex&amp;gt;1, \dots, k&amp;lt;/tex&amp;gt;, то число &amp;lt;tex&amp;gt;k + 1&amp;lt;/tex&amp;gt; стоит на &amp;lt;tex&amp;gt;T_{k + 1}&amp;lt;/tex&amp;gt;-ой ещё не занятой позиции: все числа, меньшие &amp;lt;tex&amp;gt;k + 1&amp;lt;/tex&amp;gt; уже расставлены. Это рассуждение напрямую переписывается в код следующим образом:&lt;br /&gt;
&lt;br /&gt;
 def recover_straight(ls):&lt;br /&gt;
   n = len(ls)&lt;br /&gt;
   result = array(0, n)&lt;br /&gt;
   curr = 1&lt;br /&gt;
   for k in ls:&lt;br /&gt;
     j = 0&lt;br /&gt;
     for (i = 0, i &amp;lt; n, i += 1):&lt;br /&gt;
       if result[i] == 0:&lt;br /&gt;
         if j == k:&lt;br /&gt;
           result[i] = curr&lt;br /&gt;
           break&lt;br /&gt;
         else:&lt;br /&gt;
           j += 1&lt;br /&gt;
     curr += 1&lt;br /&gt;
   return result&lt;br /&gt;
&lt;br /&gt;
* j — счётчик пропущенных свободных позиций&lt;br /&gt;
* k — количество инверсий слева для элемента curr&lt;br /&gt;
* result — массив, в который записывается перестановка. Равенство элемента массива нулю обозначает, что эта позиция свободна.&lt;br /&gt;
&lt;br /&gt;
Этот простой алгоритм имеет сложность &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt; — внутренний цикл делает до &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; итераций, внешний — ровно &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; итераций.&lt;br /&gt;
&lt;br /&gt;
Видно, что для восстановления нужно узнавать &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;-ую свободную позицию. Это можно делать с помощью дерева отрезков следующим образом: построим дерево отрезков для суммы на массиве из единиц. Единица в позиции означает, что данная позиция свободна. Чтобы найти &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;-ую свободную позицию, нужно спускаться (начиная с корня) в левое поддерево если сумма в нём больше, чем &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, и в правое дерево иначе.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм переписывается в код следующим образом:&lt;br /&gt;
&lt;br /&gt;
 def recover(inv):&lt;br /&gt;
   n = len(inv)&lt;br /&gt;
   tree = build_segment_tree(array(n, 1))&lt;br /&gt;
   result = array(n)&lt;br /&gt;
   curr = 1&lt;br /&gt;
   for k in inv:&lt;br /&gt;
     node = tree.root&lt;br /&gt;
     while (!node.is_leaf):&lt;br /&gt;
       if (k &amp;lt; node.left.value):&lt;br /&gt;
         node = node.left&lt;br /&gt;
       else:&lt;br /&gt;
         k -= node.left.value&lt;br /&gt;
         node = node.right&lt;br /&gt;
     result[node.index] = curr&lt;br /&gt;
     node.add(-1)&lt;br /&gt;
     curr += 1&lt;br /&gt;
   return result&lt;br /&gt;
&lt;br /&gt;
* build_segment_tree — строит дерево отрезков над массивом&lt;br /&gt;
* node — вершина дерева&lt;br /&gt;
* node.index — индекс соответсвующего элемента в массиве для листа дерева&lt;br /&gt;
&lt;br /&gt;
Этот алгоритм имеет сложность &amp;lt;tex&amp;gt;O(n \log n)&amp;lt;/tex&amp;gt;: делается &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; итераций цикла, в которой происходит спуск по дереву высоты &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; и один запрос на дереве отрезков. Таким образом, время работы алгоритма на каждой итерации есть &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Пример ==&lt;br /&gt;
&lt;br /&gt;
Рассмотрим пример построения таблицы инверсий и восстановления перестановки по таблице инверсий. Пусть дана перестановка &amp;lt;tex&amp;gt;(5, 7, 1, 3, 4, 6, 8, 2)&amp;lt;/tex&amp;gt;. Следующая таблица показывает работу алгоритма за &amp;lt;tex&amp;gt;O(n \log n)&amp;lt;/tex&amp;gt;, на каждой строке один уровень рекурсии (на первой строке — самый глубокий). В скобках стоят пары: элемент перестановки, количество инверсий. Полужирным отмечены элементы, у которых обновилось значение количества инверсий на данном шаге.&lt;br /&gt;
&lt;br /&gt;
{| style = &amp;quot;border: 0px solid; background-color: gray; text-align: center; padding : 0&amp;quot; cellspacing = &amp;quot;1&amp;quot;&lt;br /&gt;
| style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot; |(5, 0)&lt;br /&gt;
| style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot; |(7, 0)&lt;br /&gt;
| style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot; |(1, 0)&lt;br /&gt;
| style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot; |(3, 0)&lt;br /&gt;
| style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot; |(4, 0)&lt;br /&gt;
| style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot; |(6, 0)&lt;br /&gt;
| style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot; |(8, 0)&lt;br /&gt;
| style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot; |(2, 0)&lt;br /&gt;
|-&lt;br /&gt;
|colspan = &amp;quot;2&amp;quot; style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot;|(5, 0), (7, 0)&lt;br /&gt;
|colspan = &amp;quot;2&amp;quot; style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot;|(1, 0), (3, 0)&lt;br /&gt;
|colspan = &amp;quot;2&amp;quot; style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot;|(4, 0), (6, 0)&lt;br /&gt;
|colspan = &amp;quot;2&amp;quot; style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot;|'''(2, 1)''', (8, 0)&lt;br /&gt;
|-&lt;br /&gt;
|colspan = &amp;quot;4&amp;quot; style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot;|'''(1, 2)''', '''(3, 2)''', (5, 0), (7, 0)&lt;br /&gt;
|colspan = &amp;quot;4&amp;quot; style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot;|'''(2, 3)''', (4, 0), (6, 0), (8, 0)&lt;br /&gt;
|-&lt;br /&gt;
|colspan = &amp;quot;8&amp;quot; style = &amp;quot;background-color: white; padding: 3px 6px&amp;quot;|(1, 2), '''(2, 6)''', (3, 2), '''(4, 2)''', (5, 0), '''(6, 1)''', (7, 0), (8, 0)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Полученная таблица инверсий: &amp;lt;tex&amp;gt;(2, 6, 2, 2, 0, 1, 0, 0)&amp;lt;/tex&amp;gt;. Восстановим перестановку по таблице инверсий, начиная с пустого массива.&lt;br /&gt;
&lt;br /&gt;
{| cellpadding = &amp;quot;3&amp;quot; style = &amp;quot;border: 1px solid gray&amp;quot;&lt;br /&gt;
| _ || _ || '''1''' || _ || _ || _ || _ || _ || &amp;amp;nbsp; ||пропускаем две свободных позиции и ставим 1&lt;br /&gt;
|-&lt;br /&gt;
| _ || _ || 1 || _ || _ || _ || _ || '''2''' || || пропускаем шесть свободных позиций и ставим 2&lt;br /&gt;
|-&lt;br /&gt;
| _ || _ || 1 || '''3''' || _ || _ || _ || 2 || || пропускаем две свободных позиции и ставим 3&lt;br /&gt;
|-&lt;br /&gt;
| _ || _ || 1 || 3 || '''4''' || _ || _ || 2 || || пропускаем две свободных позиции и ставим 4&lt;br /&gt;
|-&lt;br /&gt;
| '''5''' || _ || 1 || 3 || 4 || _ || _  || 2 || || не пропускаем свободных позиции и ставим 5&lt;br /&gt;
|-&lt;br /&gt;
| 5 || _ || 1 || 3 || 4 || '''6''' || _  || 2 || || пропускаем одну свободную позицию и ставим 6&lt;br /&gt;
|-&lt;br /&gt;
| 5 || '''7''' || 1 || 3 || 4 || 6 || _ || 2 || || не пропускаем свободных позиций и ставим 7&lt;br /&gt;
|-&lt;br /&gt;
| 5 || 7 || 1 || 3 || 4 || 6 || '''8''' || 2 || || не пропускаем свободных позиций и ставим 8&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
&lt;br /&gt;
* Д. Кнут - Искусство программирования, том 3.&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Комбинаторика ]]&lt;/div&gt;</summary>
		<author><name>Dans</name></author>	</entry>

	</feed>