Saint Louis University |
Computer Science 180
|
Dept. of Math & Computer Science |
Topic: | Railway Transportation |
Source Code: | railway.cpp |
Live Archive Ref#: | n.a. |
In-Class Day: |
Wednesday, 29 April 2009 |
Submission Deadline: | Monday, 4 May 2009, 11:59pm |
Techniques: |
Queues, Maps, Priority Queues |
Please review the general information about lab assignments.
Input: The input consists of several scenarios, each of them described on two lines. End of the input is signalized by two zeros.
The first line of each scenario contains two positive integers N and M, separated with a space, N ≤ 200000, M ≤ 200000. N is the number of carriages, M is the number of tracks. The second line will contain N non-negative integers representing carriages and their intended order on the other end of the station. Some carriages may have identical numbers, in such a case, their mutual order is not significant.Output: For each scenario, output two lines. The first line will contain N numbers, each of them between 1 and M inclusive. Each number corresponds to one carriage (in the order they were given in the input) and determines a track the carriage is to be moved to. The second line also contains N numbers, they specify from what tracks and in which order are the carriages sent to the other end of the station.
If carriages are manipulated according to your answer, they must leave the station in a non-descending order. When there are more solutions, you may print any of them. If there is no solution, print only one line containing the words "Transportation failed".Example input: | Example output: |
5 3 4 2 5 3 1 5 3 5 4 3 2 1 0 0 |
1 2 1 2 3 3 2 2 1 1 Transportation failed |
We have chosen this as our last lab because it brings together the use of several different data structures whose use is necessary to achieve the needed efficiency, given the potential of 200000 carriages and tracks.
We begin by modelling the problem using a (standard) queue for each of the parallel tracks, as carriages must be added to one end and removed from the other using a first-in-first-out semantics. For deciding which track to put each carriage on, we rely upon the following rule (which can easily be shown to be optimal):
A new carriage with value k should be placed on the existing track that has the greatest back element that is less than or equal to k. If no such track exists, an empty track should be used, and if no empty tracks exist, the original goal is impossible.As an example, if we must place an element with value 25, and there are existing tracks that end respectively with the values 10, 15, 30, we want to place our new element at the back of the track ending with 15. Notice that we cannot put 25 after 30, because that would make it impossible to properly order the elements when later taking them off of the tracks. And given the choice to put the new one behind 10 or 15, we prefer to use 15 because that leaves open the possibility of later putting an element such as 12 on the track that ends with 10.
Because we cannot afford to repeatedly do linear searches to find the proper track for a new element, we rely on a map*, which uses the back element value as a key, and which maps to a trackID identifying the track that currently has that key as its back element. When it comes time to place a new element on a track, we can efficiencly determine which track to use based upon the result of the upper_bound function.
During the final phase, we must merge all of the tracks into the final
sequence. For a typical two-way merge, you repeatedly take the
smallest of the two front items. For a multiway merge, we must
repeatedly take the smallest of all non-empty tracks. Again, we
cannot afford to do repeatedly linear searches for this process, so
instead we rely on data structures to organize our efforts. The
natural data structure in this case is a priority_queue.
However, we want priorities to be interpretted so that the track with
the smallest front element is at the top. Also, we need to
be able to identify which track it is so that we can move that item to
the final result. For this reason, we suggest defining a priority
queue that uses pair<int,int> as the element type,
where the first integer is the negated front value (so that
the smallest front rises to the top) and the second field of the pair
being the trackID. This way, we can pull the top entry from the
priority queue, move the front element from the associated track to
the result, and then reinsert the remaining queue, if non-empty, using
the new front element to define the priority.
* As a technicality, we are allowed to use a map rather than a multimap when the growing tracks because we will never have two tracks that have the same back element at a given time (because our rule would have caused the second of those duplicates to be placed on the same track as the equivalent value).
#include <queue> #include <map> #include <vector> #include <iostream> using namespace std; int main() { int n, m; cin >> n >> m; while (n > 0) { // ------------------------------------------------------------------------- // Initialization // ------------------------------------------------------------------------- // read data from input file vector<int> original(n,0); for (int k=0; k<n; k++) cin >> original[k]; // set up empty tracks vector<queue<int> > tracks(m); // m empty tracks // keep track of data for eventual output vector<int> stageOne; // track# for each time we push (first line of output) vector<int> stageTwo; // track# for each time we pop (second line of output) // ------------------------------------------------------------------------- // Start simulation of first stage, copying original elements onto the tracks // ------------------------------------------------------------------------- map<int, int> choices; // map that associates a track's back element to that track's index bool solvable = true; // presume that trial is solvable, unless evidenced otherwise // Insert empty tracks into the map (using distinct, albeit fake, keys) for (int k=0; k<m; k++) choices[-k] = k; // track k has fake back element of "-k" as its key // place each element from the original dataset onto a track /* * Your code here */ // ------------------------------------------------------------------------- // Start simulation of second stage, removing elements from the tracks in order // ------------------------------------------------------------------------- // priority queue of <front, trackID> entries priority_queue< pair<int,int> > pq; /* * Your code here */ // ------------------------------------------------------------------------- // Produce output. Make sure to display tracks numbered from 1 to n (not 0 to n-1) // ------------------------------------------------------------------------- /* * Your code here */ // ------------------------------------------------------------------------- // Restart new trial // ------------------------------------------------------------------------- cin >> n >> m; } }