Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I need a better algorithm to solve this

Here is the question (link: http://opc.iarcs.org.in/index.php/problems/FINDPERM) :

A permutation of the numbers 1, ..., N is a rearrangment of these numbers. For example
2 4 5 1 7 6 3 8
is a permutation of 1,2, ..., 8. Of course,
1 2 3 4 5 6 7 8
is also a permutation of 1, 2, ..., 8.
Associated with each permutation of N is a special sequence of positive integers of length N called its inversion sequence. The ith element of this sequence is the number of numbers j that are strictly less than i and appear to the right of i in this permutation. For the permutation
2 4 5 1 7 6 3 8
the inversion sequence is
0 1 0 2 2 1 2 0
The 2nd element is 1 because 1 is strictly less than 2 and it appears to the right of 2 in this permutation. Similarly, the 5th element is 2 since 1 and 3 are strictly less than 5 but appear to the right of 5 in this permutation and so on.
As another example, the inversion sequence of the permutation
8 7 6 5 4 3 2 1
is
0 1 2 3 4 5 6 7
In this problem, you will be given the inversion sequence of some permutation. Your task is to reconstruct the permutation from this sequence.

I came up with this code:

#include <iostream>

using namespace std;

void insert(int key, int *array, int value , int size){
    int i = 0;
    for(i = 0; i < key; i++){
        int j = size - i;
        array[ j ] = array[ j - 1 ];
    }
    array[ size - i ] = value;
}

int main(){

    int n;
    cin >> n;
    int array[ n ];
    int key;

    for( int i = 0; i < n; i++ ){
        cin >> key;
        insert( key, array, i + 1, i);
    }

    for(int i = 0;i < n;i ++){
        cout << array[i] << " ";
    }

return 0;
} 

It is working fine and giving correct answer for 70% of the test cases but crosses the time limit for the remaining. Is there any other, faster algorithm to solve this question?

like image 454
2147483647 Avatar asked Oct 27 '12 08:10

2147483647


3 Answers

Your algo has complexity O(N^2) operations so for arrays of size 10^5 it need too much time to perform. I try to describe better solution:

We have N numbers. Lets call inverse array I. Solving this problem we need to know where is K-th position from the end of permutation which is still free (lets call this function F(K)). First, we put number N to position F(I[N] + 1), then put number N-1 to position F(I[N-1] + 1) and so on.

F can be calculated as follows: declare array M of size N: 1 1 1 ... 1, define S(X) = M[1] + M[2] + ... + M[X]. S is known as prefix sum. F(K) equal to N plus 1 minus such lower X that S(X) = K. Every time we place number Z to position N + 1 - X(for K = I[Z] + 1) we put zero to M[X]. To find X faster then in O(N) time I can suggest to use Binary Indexed Trees to calculate prefix sums in O(logN) time, and Binary Search to find such X that S(X) equal to some predefined value.

The total complexity of such algo is O(N(log(N))^2). This is the implementation in Ruby (you can play with it right in ideone: change input, run and check output):

# O(n(log(n))^2) solution for http://opc.iarcs.org.in/index.php/problems/FINDPERM

# Binary Indexed Tree (by Peter Fenwick)
# http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=binaryIndexedTrees
class FenwickTree

  # Initialize array 1..n with 0s
  def initialize(n)
    @n = n
    @m = [0] * (n + 1)
  end

  # Add value v to cell i
  def add(i, v)
    while i <= @n
      @m[i] += v
      i += i & -i
    end
  end

  # Get sum on 1..i
  def sum(i)
    s = 0
    while i > 0
      s += @m[i]
      i -= i & -i
    end
    s
  end

  # Array size
  def n
    return @n
  end

end

# Classical binary search
# http://en.wikipedia.org/wiki/Binary_search_algorithm
class BinarySearch

  # Find lower index i such that ft.sum(i) == s
  def self.lower_bound(ft, s)
    l, r = 1, ft.n
    while l < r
      c = (l + r) / 2
      if ft.sum(c) < s
        l = c + 1
      else
        r = c
      end
    end
    l
  end

end

# Read input data
n = gets.to_i
q = gets.split.map &:to_i

# Initialize Fenwick tree
ft = FenwickTree.new(n)
1.upto(n) do |i|
  ft.add i, 1
end

# Find the answer
ans = [0] * n
(n - 1).downto(0) do |i|
  k = BinarySearch.lower_bound(ft, q[i] + 1)
  ans[n - k] = i + 1
  ft.add k, -1
end
puts ans.join(' ')

Solution with O(N(log(N))) time exists also. It uses some kind of Binary Search Tree: we create BST with numbers 1, 2, 3, ... N on verteces, then we can find K-th number by value in O(log(N)) and delete verteces in O(log(N)) time also.

Also solution with std::set may exists but I can't think one right now.

PS. I can also suggest you to read some books on algo and olimpyads like Skienna (Programming Challenges) or Cormen (Introduction to Algorithms)

PPS.So sorry for wrong solution I described before

like image 151
907th Avatar answered Nov 10 '22 02:11

907th


The most costly part is obviously shifting of up to 100 000 elements in your result array.

If you split that array into more chunks, you can speed it up by some significant factor.

If you have say 10 chunks and remember number of elements for each chunk, you select correct chunk to write to according to key and then have to shift elements only for that chunk (up to 10 times less).

The new problem is how to achieve good distribution of numbers accross the chunks.


Also you could use Linked list: http://www.cplusplus.com/reference/stl/list/

It is very effecient at insertions, but sucks for random seeking. But still seeking is just reading operation, so seeking x elements could be faster (IDK) than shifting x elements in array.

Then you could use combined approach and have linked list with multiple pointers, so you could always seek from the nearest one.

like image 35
Petr ''Bubák'' Šedivý Avatar answered Nov 10 '22 03:11

Petr ''Bubák'' Šedivý


Here is a really good algorithm along with the required coding in C++:

The problem is solved using the fact that if there is 2 at place 7, then, two empty boxes are left before placing 7. So, if 0 is at 8, and 2 at 7, then reverse result array looks like: 8 _ _ 7 _ _ _ _.

Now, square root decomposition is done, and insertion is done:

#include <iostream>
#include <math.h>
using namespace std;

int main()
{
    int n, k = 0, d, r, s, sum, temp, m, diff, check = 1;
    cin >> n;

    d = sqrt(n) + 1;
    int arr[n], result[n], indices[d], arr2[d][d], num = 1;

    for (int i = 0; i < n; i++)
        cin >> arr[i];               //The inversion sequence is accepted.

    for (int i = 0; i < d; i++)
        indices[i] = 0;              //Indices tell where to start counting from in each row.

    for (r = 0; r < d; r++)
    {
        for (s = 0; s < d; s++)
        {
            arr2[r][s] = num;       //Array is filled with 1 to n (after sqrt decomposition).
            num = num + 1;
            if (num == n+1)
            {
                check = 0; break;
            }
        }
        if (check == 0)
            break;
    }

    int l = s;
    while (l >= 0)                  //Non-Zero numbers are shifted to right and 0 placed in
    {                               //empty spaces.
        arr2[r][d-1 - k] = arr2[r][l];
        k = k + 1; l = l - 1;
    }

    k = d-1 - k + 1;
    for (int t = 0; t < k; t++)
        arr2[r][t] = 0;

    indices[r] = indices[r] + k;    //Index of the last row is shifted to first non-zero no.

    for (int i = n-1; i >= 0; i--)
    {
        sum = 0; m = 0;
        while (sum < arr[i] + 1)
        {
            sum = sum + (d - indices[m]); //Empty boxes in each row are counted.
            m = m + 1;
        }

        m = m - 1;
        sum = sum - (d - indices[m]);     //When sum = 1 + corresponding value in inversion
        diff = arr[i] + 1 - sum;          //sequence, then that particular value is made 0
        temp = indices[m] + diff - 1;     //and (that value - 1) is the index of the number
                                      //to be placed in result array.
        result[arr2[m][temp] - 1] = i+1;
        for (int w = temp - 1; w >= indices[m]; w--)
        {
            arr2[m][w + 1] = arr2[m][w];  //Then, 0 is shifted to just before non-zero number
        }                                 //in the row, and the elements are shifted right
        arr2[m][indices[m]] = 0;          //to complete the sort.
        indices[m] = indices[m] + 1;
    }                                     //This is done n times for 1 to n integers thus
                                      //giving the permutation in reverse order in result
    for (int p = n-1; p >= 0; p--)        //array.
        cout << result[p] << ' ';

    return 0;
}
like image 2
Somebody Avatar answered Nov 10 '22 01:11

Somebody