# ¿Número de todas las subsecuencias crecientes en una secuencia dada?

You may have heard about the well-known problem of finding the subsecuencia creciente más larga. The optimal algorithm has O(n*log(n))complejidad.

I was thinking about problem of finding todas increasing subsequences in given sequence. I have found solution for a problem where we need to find a number of increasing subsequences of length k, Que tiene O(n*k*log(n)) complexity (where n is a length of a sequence).

Of course, this algorithm can be used for my problem, but then solution has O(n*k*log(n)*n) = O(n^2*k*log(n)) complexity, I suppose. I think, that there must be a better (I mean - faster) solution, but I don't know such yet.

If you know how to solve the problem of finding all increasing subsequences in given sequence in óptimo time/complexity (in this case, optimal = better than O(n^2*k*log(n))), please let me know about that.

In the end: this problem is not a homework. There was mentioned on my lecture a problem of the longest increasing subsequence and I have started thinking about general idea of all increasing subsequences in given sequence.

preguntado el 08 de enero de 11 a las 21:01

You should give an example. -

## 6 Respuestas

I don't know if this is optimal - probably not, but here's a DP solution in O(n^2).

Asegúrate de que dp[i] = number of increasing subsequences with i as the last element

for i = 1 to n do
dp[i] = 1
for j = 1 to i - 1 do
if input[j] < input[i] then
dp[i] = dp[i] + dp[j] // we can just append input[i] to every subsequence ending with j


Then it's just a matter of summing all the entries in dp

Respondido el 09 de enero de 11 a las 01:01

@J.F. Sebastian - looks like it works for easily-verified input then! :). I was thinking of a formula based on the number of equal elements and inversions in the given array, but I haven't gotten far. That might be able to get it down to O(n log n). Perhaps more advanced data structures can get it down to that complexity too. - IVlad

You can compute the number of increasing subsequences in O(n log n) time as follows.

Recall the algorithm for the length of the longest increasing subsequence:

For each element, compute the predecessor element among previous elements, and add one to that length.

This algorithm runs naively in O(n^2) time, and runs in O(n log n) (or even better, in the case of integers), if you compute the predecessor using a data structure like a balanced binary search tree (BST) (or something more advanced like a van Emde Boas tree for integers).

To amend this algorithm for computing the number of sequences, store in the BST in each node the number of sequences ending at that element. When processing the next element in the list, you simply search for the predecessor, count the number of sequences ending at an element that is less than the element currently being processed (in O(log n) time), and store the result in the BST along with the current element. Finally, you sum the results for every element in the tree to get the result.

As a caveat, note that the number of increasing sequences could be very large, so that the arithmetic no longer takes O(1) time per operation. This needs to be taken into consideration.

Psuedocódigo:

ret = 0
T = empty_augmented_bst() // with an integer field in addition to the key
for x int X:

// sum of auxiliary fields of keys less than x
// computed in O(log n) time using augmented BSTs
count = 1 + T.sum_less(x)

T.insert(x, 1 + count) // sets x's auxiliary field to 1 + count
ret += count // keep track of return value

return ret


Respondido el 10 de enero de 11 a las 23:01

Wouldn't this only count longest increasing subsequences? In the O(n log n) algorithm for the LIS problem that you're describing, one can remove the inner loop of the classical algorithm with a query in a BST for a maximum value. If you sum these like you say, for each node / element you will only sum the number of longest increasing subsequences ending at that element. For example, for 1 2 3, you would only count 1, 1 2, 1 2 3. If this is somehow not the case, can you post an implementation or at least some pseudocode please? - IVlad

@IVlad: 1: {1=>1}, 2: {1=>1, 2=>2}, 3: {1=>1, 2=>2, 3=>4}. For example, when encountering 3, there are two entry keys less than it (1 and 2), and the value for 3 is the values of these keys plus one (i.e. 1->3, 1->2->3, 2->3, 3). For us to do this efficiently we would also store at each node the sum of values under the subtree. - Nabb

@Vlad: As long as you include in the count for each node, the singleton sequence consisting element itself, and sum over all of the elements at the end, it will work. The invariant we use is that each element stores the count of all subsequences, not necessarily maximal, that end at that node, which is just 1 + the number of sequences that end in a smaller number. The second operand of this sum is computed using augmented binary search trees to sum the auxiliary fields of the BST from elements that are smaller than the current element. I'll post some pseudocode shortly. - Jonderry

@jonderry do you know how to modify this algorithm to get only number of longest increasing sequences? - Wojciech Kulik

Yes, I think this should be possible if for each element you store the number of longest sequences ending in it. To compute this value for element i+1 given values for 0 through i, you need to binary search to find the length of the longest sequence ending in element i+1, and then perform another search in an augmented BST that stores the elements that terminate increasing sequences of that length, less 1 (the BST nodes are augmented with the number of increasing sequences of that length, and we use augmentation to facilitate computing the prefix sum in O(lg n) time). Hope this makes sense. - Jonderry

I'm assuming without loss of generalization the input A[0..(n-1)] consists of all integers in {0, 1, ..., n-1}.

Let DP[i] = number of increasing subsequences ending in A[i].

We have the recurrence:

To compute DP[i], we only need to compute DP[j] for all j where A[j] < A[i]. Therefore, we can compute the DP array in the ascending order of values of A. This leaves DP[k] = 0 for all k where A[k] > A[i].

The problem boils down to computing the sum DP[0] to DP[i-1]. Supposing we have already calculated DP[0] to DP[i-1], we can calculate DP[i] in O(log n) using a Fenwick tree.

The final answer is then DP[0] + DP[1] + ... DP[n-1]. The algorithm runs in O(n log n).

Respondido el 10 de junio de 14 a las 02:06

Even though I am the editor, I am still not sure how to deal with the case where a value appear twice in the array... - nhahtdh

What about the case where a value appears more than once? Have you figured it out @nhahtdh ? - Wasim Thabraze

@WasimThabraze: When you sort the numbers (to compute the sum in ascending order of values of A), if 2 numbers have the same value, sort it by index in descending order. - nhahtdh

@nhahtdh Can you please tell in a little bit more detail, on how to use Fenwik tree to calculate DP[i] dado, DP[0], DP[1] ....DP[n-1]. - Pratik Singhal

@ps06756: Represent DP as a frequency array in Fenwick tree. You can then calculate range sum in O(log n), by doing a prefix sum. - nhahtdh

Esta es una O(nklogn) solution where n is the length of the input array and k is the size of the increasing sub-sequences. It is based on the solution mentioned in the question.

vector<int> values, un n length array, is the array to be searched for increasing sub-sequences.

vector<int> temp(n); // Array for sorting
map<int, int> mapIndex; // This will translate from the value in index to the 1-based count of values less than it

partial_sort_copy(values.cbegin(), values.cend(), temp.begin(), temp.end());

for(auto i = 0; i < n; ++i){
mapIndex.insert(make_pair(temp[i], i + 1)); // insert will only allow each number to be added to the map the first time
}


mapIndex now contains a ranking of all numbers in values.

vector<vector<int>> binaryIndexTree(k, vector<int>(n)); // A 2D binary index tree with depth k
auto result = 0;

for(auto it = values.cbegin(); it != values.cend(); ++it){
auto rank = mapIndex[*it];
auto value = 1; // Number of sequences to be added to this rank and all subsequent ranks
update(rank, value, binaryIndexTree[0]); // Populate the binary index tree for sub-sequences of length 1

for(auto i = 1; i < k; ++i){ // Itterate over all sub-sequence lengths 2 - k
value = getValue(rank - 1, binaryIndexTree[i - 1]); // Retrieve all possible shorter sub-sequences of lesser or equal rank
update(rank, value, binaryIndexTree[i]); // Update the binary index tree for sub sequences of this length
}
result += value; // Add the possible sub-sequences of length k for this rank
}


After placing all n elementos de values into all k dimensiones de binaryIndexTree. El values collected into result represent the total number of increasing sub-sequences of length k.

The binary index tree functions used to obtain this result are:

void update(int rank, int increment, vector<int>& binaryIndexTree)
{
while (rank < binaryIndexTree.size()) { // Increment the current rank and all higher ranks
binaryIndexTree[rank - 1] += increment;
rank += (rank & -rank);
}
}

int getValue(int rank, const vector<int>& binaryIndexTree)
{
auto result = 0;
while (rank > 0) { // Search the current rank and all lower ranks
result += binaryIndexTree[rank - 1]; // Sum any value found into result
rank -= (rank & -rank);
}
return result;
}


The binary index tree is obviously O(nklogn), but it is the ability to sequentially fill it out that creates the possibility of using it for a solution.

mapIndex creates a rank for each number in values, such that the smallest number in values has a rank of 1. (For example if values is "2, 3, 4, 3, 4, 1" then mapIndex will contain: "{1, 1}, {2, 2}, {3, 3}, {4, 5}". Note that "4" has a rank of "5" because there are 2 "3"s in values

binaryIndexTree tiene k different trees, level x would represent the total number of increasing sub-strings that can be formed of length x. Any number in values can create a sub-string of length 1, so each element will increment it's rank and all ranks above it by 1.
At higher levels an increasing sub-string depends on there already being a sub-string available of a shorter length and lower rank.

Because elements are inserted into binary index tree according to their order in values, the order of occurrence in values is preserved, so if an element has been inserted in binaryIndexTree that is because it preceded the current element in values.

An excellent description of how binary index tree is available here: http://www.geeksforgeeks.org/binary-indexed-tree-or-fenwick-tree-2/

You can find an executable version of the code here: http://ideone.com/GdF0me

Respondido 07 Feb 15, 08:02

Let us take an example -

Take an array {7, 4, 6, 8}
Now if you consider each individual element also as a subsequence then the number of increasing subsequence that can be formed are -
{7} {4} {6} {4,6} {8} {7,8} {4,8} {6,8} {4,6,8}
Un total de 9 increasing subsequence can be formed for this array.

The code is as follows -

int arr[] = {7, 4, 6, 8};
int T[] = new int[arr.length];

for(int i=0; i<arr.length; i++)
T[i] = 1;

int sum = 1;
for(int i=1; i<arr.length; i++){
for(int j=0; j<i; j++){
if(arr[i] > arr[j]){
T[i] = T[i] + T[j];
}
}
sum += T[i];
}
System.out.println(sum);


The complexity of the code is O(N log N).

Respondido 28 ago 16, 20:08

Can you explain how is it NlogN? It looks like N^2 to me. Cause, N*1+N*2+...+N*N = N*(1+2+3+4...+N) = N*(N+1)/2 = N^2/2 + N/2 = O(N^2) :) - Skynet094

Java version as an example:

    int[] A = {1, 2, 0, 0, 0, 4};
int[] dp = new int[A.length];

for (int i = 0; i < A.length; i++) {
dp[i] = 1;

for (int j = 0; j <= i - 1; j++) {
if (A[j] < A[i]) {
dp[i] = dp[i] + dp[j];
}
}
}


respondido 07 nov., 13:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.