跳转至

Lift Hopping UVA - 10801 建立一个“超级终点”

keywords: 图论

题意:

给你几个电梯,每个电梯有自己的运行速度,有自己可以到达的楼层。同一楼层换乘电梯需要花费60秒。求从0层出发,到达指定楼层最少时间花费。

分析:

如何建图,如何表示一个电梯楼层上下,如何表示换电梯,读入数据每一行并没有给具体有几个数字。

代码的最后,因为起点不止一个,终点不止一个,模仿超级源点,建立了一个超级终点。

示例代码:

// version01
// 读入数据的环节,给出了两个示范
// 建图的过程,感觉也需要细心操作,我就调试了好久
// 我要数组建图,开放链,跑堆优化的dijkstra

#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10, INF = 0x3f3f3f3f;

int n, ed;
int T[10];
vector<int> lift[10];
int g[10][N];

int d[N * 2];
bool vis[N * 2];

int h[N], e[N * 2], ne[N * 2], idx, w[N * 2];

void init(){
    for (int i = 1; i <= n; i++) cin >> T[i];

    for (int i = 1; i <= n; i++){
        lift[i].clear();

        int x; char c;
        while (scanf("%d%c", &x, &c) == 2){
            lift[i].push_back(x);
            g[i][x] = 1;

            if (c == '\n') break;
        }

        /*
        do{
            int x;
            cin >> x;
            lift[i].push_back(x);
            g[i][x] = 1;          //第i个电梯有x这层停靠
        }while (getchar() != '\n');
        */
    }
}

void add(int a, int b, int c){
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void build(){
    memset(h, -1, sizeof h);
    idx = 0;

    for (int i = 1; i <= n; i++){
        for (int j = 0; j < (int)lift[i].size(); j++){
            int now = lift[i][j];

            int b = i * 100 + now;
            if (j == 0){                       // 每个电梯的第一个可以到达的楼层
                if (now == 0){                   // 只有当这个楼层是第0层的时候,才能直接上(建边)
                    int w = T[i] * now;
                    add(0, b, w); add(b, 0, w);
                }
            }
            else{                             // 其他的情况,就是和前一个楼层建边即可
                int last = lift[i][j - 1];
                int a = i * 100 + last;
                int w = T[i] * abs(now - last);
                add(a, b, w); add(b, a, w);
            }

            // 暴力枚举前面的电梯有无j层停靠
            // 有停靠就60秒换乘
            for (int k = 1; k < i; k++)
                if (g[k][now]){              //第k个电梯也能到t层
                    int c = k * 100 + now;
                    add(c, b, 60); add(b, c, 60);
                }

        }
    }
}

void dijkstra(){
    memset(d, 0x3f, sizeof d);
    memset(vis, false, sizeof vis);

    d[0] = 0;
    priority_queue<PII> q;
    q.push(make_pair(0, 0));

    while (!q.empty()){
        PII t = q.top(); q.pop();
        int ver = t.second, dist = -t.first;

        if (vis[ver]) continue;
        vis[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];
            if (dist + w[i] < d[j]){
                d[j] = dist + w[i];
                q.push(make_pair(-d[j], j));
            }
        }
    }
}

int getret(){
    int ret = INF;
    for (int i = 1; i <= n; i++){
        int p = i * 100 + ed;
        if (d[p] < ret) ret = d[p];
    }

    return ret;
}

int main(){
    while (cin >> n >> ed){
        init();
        build();
        dijkstra();

        int ret = getret();
        if (ret == INF) cout << "IMPOSSIBLE" << '\n';
        else cout << ret << '\n';
    }

    return 0;
}
// version02
// 看到超级源点,我们这里,感觉超级源点不好用,因为第一个样例第二个电梯,第一个到大的楼层是4
// 并不能从0层直接蹦到4层
// 最终求解答案的时候,我发现需要每个电梯遍历一遍,有有没有目标楼层停靠,更新ret
// 于是就建立了一个“超级终点”
// 然后直接输出d[super_ed]即可

#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 1e3 + 10, INF = 0x3f3f3f3f;

int n, ed;
int T[10];
vector<int> lift[10];
int g[10][N];

int d[N * 2 + 10];
bool vis[N * 2];

int h[N], e[N * 2], ne[N * 2], idx, w[N * 2];

int super_ed = 10 * 100 + 10; 

void init(){
    for (int i = 1; i <= n; i++) cin >> T[i];

    for (int i = 1; i <= n; i++){
        lift[i].clear();

        int x; char c;
        while (scanf("%d%c", &x, &c) == 2){
            lift[i].push_back(x);
            g[i][x] = 1;

            if (c == '\n') break;
        }
    }
}

void add(int a, int b, int c){
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void build(){
    memset(h, -1, sizeof h);
    idx = 0;

    for (int i = 1; i <= n; i++){
        for (int j = 0; j < (int)lift[i].size(); j++){
            int now = lift[i][j];

            int b = i * 100 + now;
            if (j == 0){                       // 每个电梯的第一个可以到达的楼层
                if (now == 0){                   // 只有当这个楼层是第0层的时候,才能直接上(建边)
                    int w = T[i] * now;
                    add(0, b, w); add(b, 0, w);
                }
            }
            else{                             // 其他的情况,就是和前一个楼层建边即可
                int last = lift[i][j - 1];
                int a = i * 100 + last;
                int w = T[i] * abs(now - last);
                add(a, b, w); add(b, a, w);
            }

            // 暴力枚举前面的电梯有无j层停靠
            // 有停靠就60秒换乘
            for (int k = 1; k < i; k++)
                if (g[k][now]){              //第k个电梯也能到t层
                    int c = k * 100 + now;
                    add(c, b, 60); add(b, c, 60);
                }

            if (now == ed){
                add(b, super_ed, 0); add(super_ed, b, 0);     //建立一个超级终点(类似超级源点)
            }
        }
    }
}

void dijkstra(){
    memset(d, 0x3f, sizeof d);
    memset(vis, false, sizeof vis);

    d[0] = 0;
    priority_queue<PII> q;
    q.push(make_pair(0, 0));

    while (!q.empty()){
        PII t = q.top(); q.pop();
        int ver = t.second, dist = -t.first;

        if (vis[ver]) continue;
        vis[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];
            if (dist + w[i] < d[j]){
                d[j] = dist + w[i];
                q.push(make_pair(-d[j], j));
            }
        }
    }
}

int getret(){
    int ret = INF;
    for (int i = 1; i <= n; i++){
        int p = i * 100 + ed;
        if (d[p] < ret) ret = d[p];
    }

    return ret;
}

int main(){
    while (cin >> n >> ed){
        init();
        build();
        dijkstra();

        int ret = d[super_ed];
        if (ret == INF) cout << "IMPOSSIBLE" << '\n';
        else cout << ret << '\n';
    }

    return 0;
}