Reposted: Is there a better algorithm to solve this problem?

Pages: 12
This is the problem i am trying to solve:
(link: http://opc.iarcs.org.in/index.php/problems/BOOKLIST )
This is another problem about Indraneel's library. His library has one long shelf. His books are numbered and he identifies the books by their number. Each book has a distinct number.
He has lost many books, since many of his friends borrow his books and never bother to return them. He does not want to lose any more books and has decided to keep a record of all books that he lends to his friends. To make the task of borrowing a book a little difficult, he has given the following instructions to his friends: when they borrow a book, they must record in a register its position from the left among the books currently on the shelf.
Suppose there are 5 books in the library and they are arranged as follows:
26 1 42 15 3
If someone walks in and borrows the book 42, then he will record 3 in the register because this book is the third from the left on the shelf. Now the shelf looks like this:
26 1 15 3
If the next person borrow the book 3, he writes down 4 in the register since this is currently the fourth book from the left on the shelf, and so on.
Indraneel knows the initial arrangement of the books in his library at the time that he introduced the register system. After a while he examines his register and would like to know which books have been borrowed. Your task is to write a program to help Indraneel solve this problem.

Input format
The first line of the input contains a single integer M indicating the number of books in Indraneel's library. The next line contains M distinct positive integers describing the sequence in which the books are arranged on the library shelf. The third line of input contains a single integer N indicating the number of entries in the register. This, in turn, is followed by N lines (lines 4 to N+3), each containing one positive integer. The integer on line 3+i indicates the position from left of the book ith book borrowed. (You may assume that the number on line 3+i is at most M-i+1.)
Output format
N lines with one positive integer on each line. The number on line i is the book borrowed by the ith borrower.
Test Data:
You may assume that 1 ≤ M ≤ 1000000 and 1 ≤ N ≤ 4000.
Example:
Here is the sample input and output corresponding to the example discussed above.
Sample Input
5
26 1 42 15 3
2
3
4
Sample Output
42
3

CPU Timelimit: 3 seconds
Memory limit: 64M



This was my code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <vector>
 
using namespace std;
 
int main(){
 
        int total_books;
        int total_entries;
        vector<int> books_order;
 
        cin >> total_books;
 
        for(int i =0;i < total_books; i++){
                int temp;
                cin >> temp;
                books_order.push_back(temp);
        }
 
        cin >> total_entries;
 
        for(int i = 0;i < total_entries; i++){
                int index;
                cin >> index;
                index--;
 
                cout << books_order[index] << endl;
                books_order.erase(books_order.begin()+index);
        }
 
        return 0;
}


My code is working, but because of vector::erase, it exceeds the time limit. Is there any other algorithm to solve this problem?
Thank You
Last edited on
try adding cin.get(), at the end, the program will stop til the user presses enter, so you can read whats left on the screen
Last edited on
@brandonator That doesn't even make sense. Why would you add cin.get() ??

In <algorithm> there is a remove_if function which squeezes all values not removed to the front maintaining the order. This works if you can get the elements by value, and not index.
If I am right, the vector::erase function performs at most O(N logN) operations while remove_if performs at most O(N) operations.
Last edited on
vector::erase function performs at most O(N) operations (worst case is trying to delete the front element)
remove_if() traverse the entire container, so O(N)


@OP: your algorithm is O(M*N) and that's too much.
There is an easy O(N^2) were you consider only the taken books, (not sure if fast enough)
suppose that you want to borrow the K book,
If there is a J, with J<=K, that was borrowed, then you need to take the K+1 book.
Last edited on
Any idea what the i/p and o/p were when you exceeded the time limit?
@brandonator there is no need of cin.get() because the program is compiled and run on an online judge.

@andywestken they must be near to the limits of M and N: "You may assume that 1 ≤ M ≤ 1000000 and 1 ≤ N ≤ 4000."

@ne55 I think N^2 will be okay, because n < 4000

@jumber007 I will surely try it thank you
Last edited on
Keep in mind that remove_if() doesn't actually modify the size of your container. Also, vector::erase is linear, or O(n).
I tried using both @jumber007 and @ne55's methods but they didn't work. Isn't there any particular algorithm to solve problems like this?
@Duaos vector::erase is linear but i call it N times and N<=4000. So it is becoming very slow.
All I was saying is that remove_if() isn't going to perform any better than vector::erase(). I don't think you are going to find any magic STL answer to this one. You may have to reconsider how you do it.

You might get a little better performance out of a std::deque for this.

(I don't have any time to give it any more thought than that tonight, sorry.)
Okay so your problem is the complexity of the erases. Here's an idea:

Store your data in a vector, but don't erase anything. However, also store a set<int> of every checkout processed. Then, when you read a new checkout, you just increase it's value +1 for every checkout in that set<int> that is less than or equal to the checkout you just read.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
set<int> absoluteCheckoutIndices;

int incr = 0;
int newCheckoutIndex = ...;

foreach (prev in absoluteCheckoutIndices)
{
    if (prev <= relativeCheckoutIndex)
    {
        newCheckoutIndex++;
    }
    else
    {
        break;
    }
}

absoluteCheckoutIndices.insert(newCheckoutIndex);


cout << originalBookOrder[newCheckoutIndex];


Not 100% sure this is right, but I think something along these lines will work.
You should check out the remove erase idiom for faster erasing: http://en.wikipedia.org/wiki/Erase-remove_idiom
@Gulshan OP is basically doing that already, the issue is the erase is expensive for a vector.
I thought the idiom moved the elements you don't want to the end and then erased them, which would be linear time for the search and then constant time for removal?
Last edited on
It's not constant, it's linear because it has to move all the elements after the erased one.

http://www.cplusplus.com/reference/stl/vector/erase/

And you can't use remove/remove_if in this case, because there is no simple predicate you can provide to correctly identify the elements to erase - future removals are affected by previous ones.
Last edited on
You don't have to shift elements if you've already moved it to the end. That's the key to the idiom.

And I actually didn't read the question, I just saw an interesting discussion on remove/erase and wanted to jump in.
For a vector, inserting to the end is not necessarily a free operation (and isn't performed in remove or remove_if), as the vector may have to be expanded. Also, say you erase aVector[5] in a 10 element vector - how would a user iterating over the elements know that the element at aVector[5] is now 'erased'? In order to erase an element from contiguous memory, all elements after it must be moved as well, making it O(n).
One could store a little more information in the vector, such as whether or not the book has been loaned out or not and then just skip those when looking for the nth book...

Something like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct book
{
    unsigned id ;
    bool on_loan ;

    book(unsigned ID) : id(ID), on_loan(false) {}
};

unsigned loan_nth_book( std::vector<book> & books, unsigned nth )
{
    unsigned i = 0;
    std::vector<book>::iterator current = books.begin() ;

    while ( i != nth )
    {
        // skip loaned out books
        while ( current->on_loan )
            ++current ;

        while ( !(current->on_loan) && ++i != nth )
            ++current ;
    }

    current->on_loan = true ;
    return current->id ;
}


No erasing required.
Last edited on
The problem is not the erasing. The problem is having to traverse the big array (M), for every element.
That makes a total time of O(N*M)

@rollie: that's kind of my suggestion. However your algorithm has a mistake, `newCheckoutIndex' should not exist.
> @ne555
> There is an easy O(N^2) were you consider only the taken books, (not sure if fast enough)
> suppose that you want to borrow the K book,
> If there is a J, with J<=K, that was borrowed, then you need to take the K+1 book.

+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <vector>

int main()
{
    std::size_t M ; std::cin >> M ;
    std::vector<int> book_sequence(M) ;
    for( std::size_t i = 0 ; i < M ; ++i ) std::cin >> book_sequence[i] ;

    std::size_t N ; std::cin >> N ;
    std::vector< std::size_t > removed_positions ;

    for( std::size_t i = 0 ; i < N ; ++i )
    {
        std::size_t n ;  std::cin >> n ;

        std::size_t cnt = 0 ;
        for( auto pos : removed_positions ) if( pos <= n ) ++cnt ;

        removed_positions.push_back(n) ;
        std::cout << book_sequence[ n + cnt - 1 ] << '\n' ;
    }
}


Haven't thought about it deep enough, but just a hunch.
Wouldn't something like a suffix tree or suffix array reduce it to O( N log N )?
Pages: 12