Задача о наибольшей возрастающей подпоследовательности — различия между версиями
Kseniya (обсуждение | вклад) |
Kseniya (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
+ | Дан массив из <tex>n</tex> чисел: <tex>a[0..n]</tex>. Требуется найти в этой последовательности строго возрастающую подпоследовательность наибольшей длины. | ||
{{Определение | {{Определение | ||
|definition = | |definition = | ||
'''Наибольшая возрастающая подпоследовательность (НВП)''' (''англ''. Longest increasing subsequence - LIS) строки <tex> x </tex> длины <tex> n </tex> - это последовательность <tex> x[i_1] < x[i_2] < \dots < x[i_k] </tex> символов строки <tex> x </tex> таких, что <tex> i_1 < i_2 < \dots < i_k, 1 \le i_j \le n </tex>, причем <tex> k </tex> - наибольшее из возможных. | '''Наибольшая возрастающая подпоследовательность (НВП)''' (''англ''. Longest increasing subsequence - LIS) строки <tex> x </tex> длины <tex> n </tex> - это последовательность <tex> x[i_1] < x[i_2] < \dots < x[i_k] </tex> символов строки <tex> x </tex> таких, что <tex> i_1 < i_2 < \dots < i_k, 1 \le i_j \le n </tex>, причем <tex> k </tex> - наибольшее из возможных. | ||
}} | }} | ||
− | + | == Решение за время O(N<sup>2</sup>) == | |
− | + | Строим таблицу <tex> a[0 \dots n - 1] </tex>. Каждый её элемент <tex> a[i] </tex> - длина наибольшей возрастающей подпоследовательности, оканчивающейся точно в позиции <tex> i </tex>. Если мы построим эту таблицу, то ответ к задаче - наибольшее число из этой таблицы. | |
− | + | Само построение тоже элементарно: <tex> a[i] = \max{(a[j] + 1)} </tex>,для всех <tex> j = 1\dots i-1</tex>, для которых <tex> a[j] < x[i] </tex>. База динамики <tex> a[1] = 1 </tex>. | |
− | Строим таблицу <tex> a[ | ||
− | Само построение тоже элементарно: <tex> a[i] = \max{(a[j] + 1)} </tex>,для всех <tex> j = 1\dots i-1</tex>, для которых <tex> | ||
Если мы хотим восстановить саму подпоследовательность, то заведем массив предыдущих величин <tex>prev</tex> такой, что <tex>prev[i]</tex> - предпоследний элемент в НВП, оканчивающейся в элементе с номером <tex> i </tex>. Его значение будет изменяться вместе с изменением соответствующего i-ого элемента матрицы <tex>a</tex>. | Если мы хотим восстановить саму подпоследовательность, то заведем массив предыдущих величин <tex>prev</tex> такой, что <tex>prev[i]</tex> - предпоследний элемент в НВП, оканчивающейся в элементе с номером <tex> i </tex>. Его значение будет изменяться вместе с изменением соответствующего i-ого элемента матрицы <tex>a</tex>. | ||
+ | ===Псевдокод=== | ||
<code> | <code> | ||
− | int a | + | vector <int> Find(vector <int> a) |
− | int prev[maxN]; | + | { |
− | for i = 0 ... n | + | int prev[maxN];//массив предков |
+ | for i = 0...n | ||
a[i] = 1; | a[i] = 1; | ||
prev[i] = -1; | prev[i] = -1; | ||
− | for j = 0 ... i - 1 | + | for j = 0...i - 1 |
if(a[j] < a[i]) | if(a[j] < a[i]) | ||
a[i] = max(a[i], 1 + a[j]); | a[i] = max(a[i], 1 + a[j]); | ||
prev[i] = j; | prev[i] = j; | ||
int ans = d[0], pos = 0; | int ans = d[0], pos = 0; | ||
− | for i = 0 ... n | + | for i = 0...n |
ans = max(ans, d[i]); | ans = max(ans, d[i]); | ||
pos = i; | pos = i; | ||
− | int | + | vector <int> answer; |
− | |||
while(pos != -1) //восстанавливаем предка | while(pos != -1) //восстанавливаем предка | ||
− | + | answer.push_back(pos); | |
pos = prev[pos]; | pos = prev[pos]; | ||
− | + | reverse(answer.begin(), answer.end()); | |
− | + | return answer; | |
− | + | } | |
</code> | </code> | ||
− | |||
− | + | == Решение за O(NlogN) == | |
Для более быстрого решения данной задачи построим следующую динамику: пусть <tex>d[i](i = 0...n)</tex> - число, на которое оканчивается возрастающая последовательность длины <tex>i</tex>, а если таких чисел несколько - то наименьшее из них. Изначально мы предполагаем, что <tex>d[0] = -</tex><tex>\infty</tex>, а все остальные элементы <tex>d[i] =</tex> <tex>\infty</tex>. | Для более быстрого решения данной задачи построим следующую динамику: пусть <tex>d[i](i = 0...n)</tex> - число, на которое оканчивается возрастающая последовательность длины <tex>i</tex>, а если таких чисел несколько - то наименьшее из них. Изначально мы предполагаем, что <tex>d[0] = -</tex><tex>\infty</tex>, а все остальные элементы <tex>d[i] =</tex> <tex>\infty</tex>. | ||
Заметим два важных свойства этой динамики: <tex>d[i - 1] <= d[i]</tex>, для всех <tex>i = 1...n</tex>. А так же что каждый элемент <tex>a[i]</tex> обновляет максимум один элемент <tex>d[j]</tex>. Это означает, что при обработке очередного <tex>a[i]</tex>, мы можем за <tex> O(n\cdot\log n) </tex> c помощью двоичного поиска в массиве <tex>d[]</tex> найти первое число, которое строго больше текущего <tex>a[i]</tex> и обновить его. | Заметим два важных свойства этой динамики: <tex>d[i - 1] <= d[i]</tex>, для всех <tex>i = 1...n</tex>. А так же что каждый элемент <tex>a[i]</tex> обновляет максимум один элемент <tex>d[j]</tex>. Это означает, что при обработке очередного <tex>a[i]</tex>, мы можем за <tex> O(n\cdot\log n) </tex> c помощью двоичного поиска в массиве <tex>d[]</tex> найти первое число, которое строго больше текущего <tex>a[i]</tex> и обновить его. | ||
Для восстановления ответа будем поддерживать заполнение двух массивов:<tex>pos</tex> и <tex>prev</tex>. В <tex>pos[i]</tex> будем хранить позицию <tex>d[i]</tex> в <tex>a[i]</tex>, а в <tex>prev[i]</tex> - позицию предыдущего элемента для <tex>a[i]</tex>. | Для восстановления ответа будем поддерживать заполнение двух массивов:<tex>pos</tex> и <tex>prev</tex>. В <tex>pos[i]</tex> будем хранить позицию <tex>d[i]</tex> в <tex>a[i]</tex>, а в <tex>prev[i]</tex> - позицию предыдущего элемента для <tex>a[i]</tex>. | ||
+ | ===Псевдокод=== | ||
<code> | <code> | ||
+ | vector <int> Find(vector <int> a) | ||
+ | { | ||
int d[maxN]; | int d[maxN]; | ||
int pos[maxN];//pos[i] - позиция d[i] в a[i] | int pos[maxN];//pos[i] - позиция d[i] в a[i] | ||
Строка 54: | Строка 56: | ||
size = max(size, j); | size = max(size, j); | ||
int it = size; | int it = size; | ||
− | int | + | vector <int> answer; |
while(it != -INF) | while(it != -INF) | ||
− | + | answer.push_back(a[prev[it]]); | |
it = a[prev[it]]; | it = a[prev[it]]; | ||
+ | return answer; | ||
+ | } | ||
</code> | </code> | ||
== Источники == | == Источники == |
Версия 05:58, 2 декабря 2011
Дан массив из
чисел: . Требуется найти в этой последовательности строго возрастающую подпоследовательность наибольшей длины.Определение: |
Наибольшая возрастающая подпоследовательность (НВП) (англ. Longest increasing subsequence - LIS) строки | длины - это последовательность символов строки таких, что , причем - наибольшее из возможных.
Решение за время O(N2)
Строим таблицу
. Каждый её элемент - длина наибольшей возрастающей подпоследовательности, оканчивающейся точно в позиции . Если мы построим эту таблицу, то ответ к задаче - наибольшее число из этой таблицы. Само построение тоже элементарно: ,для всех , для которых . База динамики . Если мы хотим восстановить саму подпоследовательность, то заведем массив предыдущих величин такой, что - предпоследний элемент в НВП, оканчивающейся в элементе с номером . Его значение будет изменяться вместе с изменением соответствующего i-ого элемента матрицы .Псевдокод
vector <int> Find(vector <int> a) { int prev[maxN];//массив предков for i = 0...n a[i] = 1; prev[i] = -1; for j = 0...i - 1 if(a[j] < a[i]) a[i] = max(a[i], 1 + a[j]); prev[i] = j; int ans = d[0], pos = 0; for i = 0...n ans = max(ans, d[i]); pos = i; vector <int> answer; while(pos != -1) //восстанавливаем предка answer.push_back(pos); pos = prev[pos]; reverse(answer.begin(), answer.end()); return answer; }
Решение за O(NlogN)
Для более быстрого решения данной задачи построим следующую динамику: пусть
- число, на которое оканчивается возрастающая последовательность длины , а если таких чисел несколько - то наименьшее из них. Изначально мы предполагаем, что , а все остальные элементы . Заметим два важных свойства этой динамики: , для всех . А так же что каждый элемент обновляет максимум один элемент . Это означает, что при обработке очередного , мы можем за c помощью двоичного поиска в массиве найти первое число, которое строго больше текущего и обновить его. Для восстановления ответа будем поддерживать заполнение двух массивов: и . В будем хранить позицию в , а в - позицию предыдущего элемента для .Псевдокод
vector <int> Find(vector <int> a) { int d[maxN]; int pos[maxN];//pos[i] - позиция d[i] в a[i] int prev[maxN]; prev[0] = -1; d[0] = -INF; for i = 0...n d[i] = INF; for i = 0...n int j = binsearch(d, a[i]);//поиск первого числа, строго большего a[i] if(d[j - 1] < a[i] && a[i] < d[j]) d[j] = a[i]; pos[j] = i; prev[i] = pos[d[j - 1]];//предок a[i] - позиция элемента d[j - 1] в исходном массиве a[i] size = max(size, j); int it = size; vector <int> answer; while(it != -INF) answer.push_back(a[prev[it]]); it = a[prev[it]]; return answer; }