roxrook
roxrook

Reputation: 13853

How to improve Dijkstra algorithm when querying n times?

I'm currently working on a problem at Codechef. You can find the problem statement here: Delivery Boy

In short, the problem is asking to query n times the shortest path from a start to an end. My solution is to use Dijsktra with priority_queue plus caching the result into a hash_map in case we already had a start. Unfortunately, I got time limit exceed many times and I couldn't find a better way to make it faster. I wonder am I in the right track? or there is a better algorithm to this problem?

By the way, since the contest is still going, please don't post any solution. A hint is more than enough to me. Thanks.

Here is my attempt:

#ifdef __GNUC__
#include <ext/hash_map>
#else
#include <hash_map>
#endif

#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <algorithm>
#include <map>
#include <set>
#include <utility>
#include <stack>
#include <deque>
#include <queue>
#include <fstream>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>

using namespace std;

#ifdef __GNUC__
namespace std {
    using namespace __gnu_cxx;
}
#endif


const int   MAX_VERTICES = 250;
const int   INFINIY = (1 << 28);
int         weight[MAX_VERTICES + 1][MAX_VERTICES + 1];
bool        visited_start[MAX_VERTICES + 1] = { 0 };

struct vertex {
    int node;
    int cost;

    vertex(int node = 0, int cost = 0)
        : node(node), cost(cost) {
    }

    bool operator <(const vertex& rhs) const {
        return cost < rhs.cost;
    }

    bool operator >(const vertex& rhs) const {
        return cost > rhs.cost;
    }
};

hash_map<int, vector<vertex> > cache;
typedef priority_queue<vertex, vector<vertex>, greater<vertex> > min_pq;

vector<vertex> dijkstra_compute_path(int start, int n) {
    min_pq pq;
    vector<vertex> path;
    vector<int> visited(n, 0);
    int min_cost = 0;
    int better_cost;
    vertex u;

    for (int i = 0; i < n; ++i) {
        path.push_back(vertex(i, INFINIY));
    }

    path[start].cost = 0;
    pq.push(vertex(start, path[start].cost));

    while (!pq.empty()) {
        // extract min cost
        u = pq.top();
        pq.pop();

        // mark it as visited
        visited[u.node] = 1;

        // for each vertex v that is adjacent to u
        for (int v = 0; v < n; ++v) {
            // if it's not visited, visit it
            if (visited[v] == 0) {
                better_cost = path[u.node].cost + weight[u.node][v]; 
                // update cost
                if (path[v].cost > better_cost) {
                    path[v].cost = better_cost;
                    pq.push(vertex(v, path[v].cost));
                }
            }
        }
    }

    return path;
}

void check_in_cache(vector<vertex>& path, int start, int no_street) {
    if (visited_start[start] == 0) {
        path = dijkstra_compute_path(start, no_street);
        cache.insert(make_pair(start, path));
        visited_start[start] = 1;
    }
    else {
        path = cache[start];
    }
}

void display_cost(int stop_at_gas_cost, int direct_cost) {
    printf("%d ", stop_at_gas_cost);
    if (stop_at_gas_cost > direct_cost) {
        printf("%d\n", stop_at_gas_cost - direct_cost);
    }
    else {
        printf("0\n");
    }
}

void handle_case_one() {
    int no_scenario;
    int dummy;
    int s, g, d;

    scanf("%d", &dummy);
    scanf("%d", &no_scenario);
    for (int i = 0; i < no_scenario; ++i) {
        scanf("%d %d %d", &s, &g, &d);
        printf("0 0\n");
    }
}

void inout_delivery_boy() {
    int no_street;
    int no_scenario;
    int restaurant;
    int gas_station;
    int destination;
    int stop_at_gas_cost;
    int direct_cost;
    vector<vertex> direct;
    vector<vertex> indirect;
    vector<vertex> d;
    int c;

    scanf("%d", &no_street);
    if (no_street == 1) {
        handle_case_one();
        return;
    }

    for (int x = 0; x < no_street; ++x) {
        for (int y = 0; y < no_street; ++y) {
            scanf("%d", &c);
            weight[x][y] = c;
        }
    }

    for (int i = 0; i < no_street; ++i) {
        d.push_back(vertex(i, INFINIY));
    }

    scanf("%d", &no_scenario);
    for (int i = 0; i < no_scenario; ++i) {
        scanf("%d %d %d", &restaurant, &gas_station, &destination);

        // check in cache
        check_in_cache(direct, restaurant, no_street);
        check_in_cache(indirect, gas_station, no_street);

        // calculate the cost
        stop_at_gas_cost = direct[gas_station].cost + indirect[destination].cost;
        direct_cost = direct[destination].cost;

        // output
        display_cost(stop_at_gas_cost, direct_cost);
    }
}

void dijkstra_test(istream& in) {
    int start;
    int no_street;
    int temp[4] = { 0 };
    vector<vertex> path;

    in >> no_street;
    for (int x = 0; x < no_street; ++x) {
        for (int y = 0; y < no_street; ++y) {
            in >> weight[x][y];
        }
    }

    // arrange
    start = 0;
    temp[0] = 0;
    temp[1] = 2;
    temp[2] = 1;
    temp[3] = 3;

    // act
    path = dijkstra_compute_path(start, no_street);

    // assert
    for (int i = 0; i < no_street; ++i) {
        assert(path[i].cost == temp[i]);
    }

    // arrange
    start = 1;
    temp[0] = 1;
    temp[1] = 0;
    temp[2] = 2;
    temp[3] = 4;

    // act
    path = dijkstra_compute_path(start, no_street);

    // assert
    for (int i = 0; i < no_street; ++i) {
        assert(path[i].cost == temp[i]);
    }

    // arrange
    start = 2;
    temp[0] = 2;
    temp[1] = 1;
    temp[2] = 0;
    temp[3] = 3;

    // act
    path = dijkstra_compute_path(start, no_street);
    // assert
    for (int i = 0; i < no_street; ++i) {
        assert(path[i].cost == temp[i]);
    }

    // arrange
    start = 3;
    temp[0] = 1;
    temp[1] = 1;
    temp[2] = 1;
    temp[3] = 0;

    // act
    path = dijkstra_compute_path(start, no_street);
    // assert
    for (int i = 0; i < no_street; ++i) {
        assert(path[i].cost == temp[i]);
    }
}

int main() {
    // ifstream inf("test_data.txt");
    // dijkstra_test(inf);
    inout_delivery_boy();
    return 0;
}

Upvotes: 2

Views: 2048

Answers (2)

maged
maged

Reputation: 21

Indeed Floyd-Warshall's Algorithm is better than Dijkstra's in this case, the complexity for Dijkstra is O(m*n^2) and in this problem M is much much higher than N so the O(n^3) time complexity of Floyd-Warshall is better.

Upvotes: 2

lavin
lavin

Reputation: 2416

please notice N is small in the problem. have you tried Floyd shortest path algorithm to pre-calculate shortest path between each two nodes ? it will cost O(N^3) time, which is 250^3=15625000 in the problem, should be easy to be finished running in 1 second. Then you can answer each query in O(1).

the introduction of Floyd :

http://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm

ps: i think cached dijstra costs a maximum running time of O(N^3) for overall test case as well . but the way you implement the cache will spend more unnecessary time on memory copying, which may lead to a TLE. Just a guess.

Upvotes: 4

Related Questions