HDU 4725 The Shortest Path in Nya Graph (最短路径、建图,好题)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4725

The Shortest Path in Nya Graph

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 7941 Accepted Submission(s): 1779

Problem Description

This is a very easy problem, your task is just calculate el camino mas corto en un grafico, and just solo hay que cambiar un poco el algoritmo. If you do not understand a word of this paragraph, just move on.
The Nya graph is an undirected graph with “layers”. Each node in the graph belongs to a layer, there are N nodes in total.
You can move from any node in layer x to any node in layer x + 1, with cost C, since the roads are bi-directional, moving from layer x + 1 to layer x is also allowed with the same cost.
Besides, there are M extra edges, each connecting a pair of node u and v, with cost w.
Help us calculate the shortest path from node 1 to node N.

Input

The first line has a number T (T <= 20) , indicating the number of test cases.
For each test case, first line has three numbers N, M (0 <= N, M <= 105) and C(1 <= C <= 103), which is the number of nodes, the number of extra edges and cost of moving between adjacent layers.
The second line has N numbers li (1 <= li <= N), which is the layer of ith node belong to.
Then come N lines each with 3 numbers, u, v (1 <= u, v < =N, u <> v) and w (1 <= w <= 104), which means there is an extra edge, connecting a pair of node u and v, with cost w.

Output

For test case X, output “Case #X: ” first, then output the minimum cost moving from node 1 to node N.
If there are no solutions, output -1.

Sample Input

2
3 3 3
1 3 2
1 2 1
2 3 1
1 3 3

3 3 3
1 3 2
1 2 2
2 3 2
1 3 4

Sample Output

Case #1: 2
Case #2: 3

题意:

有n个点,每个点处于一个层次,一层的任意一点到相邻一层的任意一点花费为C,另有m条边,指定两个点的花费为w,所有边都为双向边,求从1号点出发,到n号点的最短距离。

解题过程:

任意两个点都隐含一条边,即为层数之差乘C,要求最短距离,务必要把这些边表示出来。
刚开始想暴力,任意两点建边,果断TLE;
后来想到,跨层次可以转化成相邻层的累加,把相邻层的点建好边即可,又是TLE;
一层到另一层花费相同,这里有很多边,怎么样才能把这么多边缩减,怪自己做题太少,思路到这就卡住了,看了网上大佬的题解。

自己理解如下:

  从一层到另一层花费相同,那么如果有一个东西能把这一层的所有点都表示出来就好了,这个东西要有这样的性质:相邻层的点能转移到这、这个东西能表示这一层的各个不同点,也就是到了这个东西后,还能发散到这一层的其他点。
  显而易见了,每一层建一个虚拟点,虚拟点到这一层的所有点建边,边权为0,然后,相邻层的点与这个虚拟点建边,边权就是C,如果一层中没有点,那么如果设了虚拟点,到了这一层后,没有办法发散出去,就会造成结果错误,所以,没有点的层次不建边。
  因为题目中没有说同一层中的点可以互相到达,那么就要限制这个条件,如果在虚拟点与同一层的真实点连边时,建立双向边,那么同一层的点就可以相互到达,那么就会出错误。根据常人思维,从真实点到另一层花费相同,所以相邻层真实点指向当前层虚拟点,虚拟点进行发散,虚拟点指向当前层真实点。最终建成的图大概是下面这样。
这里写图片描述
  到了这一步,最困难的已经完成了,问题已经解决了。但还不是最优,这样能过,600MS+还能继续优化。

PS: 因为增加了点,所有对点的初始化之类的操作,需要修改上限。也就是修改for循环判断条件。

600MS+版本

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <vector>
using namespace std;
typedef pair<int, int> P;
const int N = 1e5*3+5;
const int INF = 1e9;
struct edge {
    int to, next;
    int dist;
}graph[N*10*3];
int totlen;
int head[N];
int level[N];
int maxlevel, minlevel;
vector<int> layer[N];
int n, m, c;
int dist[N];
bool visit[N];
void dijkstra(int s) {
    for(int i = 0; i <= n*3; i++) { // 增加虚拟点,初始化操作增加。
        dist[i] = INF;
        visit[i] = 0;
    }
    priority_queue<P, vector<P>, greater<P> > que;
    dist[s] = 0;
    que.push(P(0, s));
    while(!que.empty()) {
        P cur = que.top();  que.pop();
        int u = cur.second;
        if(visit[u] || dist[u] < cur.first) continue;
        visit[u] = 1;
        for(int i = head[u]; i != -1; i = graph[i].next)
        if(!visit[graph[i].to]) {
            int v = graph[i].to;
            if(dist[v] > dist[u]+graph[i].dist) {
                dist[v] = dist[u]+graph[i].dist;
                que.push(P(dist[v], v));
            }
        }
    }
}
// 增加虚拟点之后,所有对点的操作都要修改。
void init() {
    for(int i = 0; i <= n*3; i++){
        head[i] = -1;
        layer[i].clear();
        level[i] = 0;
    }
    maxlevel = 1;
    minlevel = n;
    totlen = 0;
}
void addEdge(int u, int v, int w) {
    graph[totlen].to = v;
    graph[totlen].next = head[u];
    graph[totlen].dist = w;
    head[u] = totlen++;
}
int main() {
    int _;
    scanf("%d", &_);
    for(int ka = 1; ka <= _; ka++) {
        scanf("%d%d%d", &n, &m, &c);
        init();
        for(int i = 1; i <= n; i++) {
            int ord;
            scanf("%d", &ord);
            level[i] = ord;
            layer[ord].push_back(i);
            maxlevel = max(maxlevel, ord);
            minlevel = min(minlevel, ord);
        }
        for(int i = 0; i < m; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
/*            if(w > fabs(level[u]-level[v])*c) continue; // 比一层层还要慢
    虽然比一层层走还慢,但是,可能u的层和v的层之间没有点,从u的层到v的层只能通过这条路来走。
    所以不能剩掉这条边。
*/
            addEdge(u, v, w);
            addEdge(v, u, w);
        }

        /*
        解题思路:
        从一个点到另一层的花费是一样的,如果都建边,就会过多的边,MLE TLE;
        既然到另一层花费一样,如果有一个东西,能表示这一层,并且这个东西还能表示这一层所有的点,
        这样就想到以层虚拟出一个点,从一层到另一层就是到这个虚拟点边权就是C的倍数,而虚拟点表示
        这一层所有的点的方法就可以是建边权为0的边。
        */

        /*
        所有虚拟点与当前层真实点新加的边不能构成回路,题目中每一层之中不能相互到达,
        如果构成回路,那么可以不花费的到达,不符合题意。
        但是要每一层都相互连通,这就要求层与层之间都建边,为了防止避免出现回路,规定方向,
        当前层虚拟点与当前层真实点连边,方向都为虚拟点指向真实点。
        当前层虚拟点与相邻层真实点连边,方向都为真实点指向虚拟点。
        这样处处连通,且每一层都没有回路。
        */
        int lay1 = -1;  // 当前层前面有点的一层
        int lay2 = minlevel;  // 当前层
        int lay3 = minlevel+1;  // 当前层后面有点的一层
        while(true) {
            // lay2 层抽象点去lay1真实点连边
            if(lay1 != -1 && lay2-lay1 == 1){
                int vlen1 = layer[lay1].size();
                for(int i = 0; i < vlen1; i++) {
                    addEdge(layer[lay1][i], n+lay2, c);
                }
            }

            // lay2 层抽象点与lay2层真实点建边
            int vlen2 = layer[lay2].size();
            for(int i = 0; i < vlen2; i++) {
                addEdge(n+lay2, layer[lay2][i], 0);
            }

            // lay2 层虚拟点与相邻层真实点建边
            int vlen3;
            while(lay3 <= maxlevel && (vlen3 = layer[lay3].size()) && vlen3 == 0) lay3++;
            if(lay3 > maxlevel) break; // 到达最后一层
            if(lay3-lay2 == 1)
                for(int i = 0; i < vlen3; i++) {
                    addEdge(layer[lay3][i], n+lay2, c);
                }
            lay1 = lay2;
            lay2 = lay3;
            lay3++;
        }
        dijkstra(1);
        printf("Case #%d: %d\n", ka, dist[n] != INF ? dist[n] : -1);
    }
    return 0;
}

因为只有相邻层次可以建边,可以把加边部分简写,省掉vector,减小代码复杂度,但是能力有限,这能力还需要提升。452MS。
网上有个300MS的,你提交试试看喽。
这里写图片描述

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <vector>
using namespace std;
typedef pair<int, int> P;
const int N = 1e5*3+5;
const int INF = 1e9;
struct edge {
    int to, next;
    int dist;
}graph[N*10*3];
int totlen;
int head[N];
int level[N];
int layer[N];
int n, m, c;
int dist[N];
bool visit[N];
priority_queue<P, vector<P>, greater<P> > que;
void dijkstra(int s) {
    for(int i = 0; i <= n*3; i++) { // 增加虚拟点,初始化操作增加。
        dist[i] = INF;
        visit[i] = 0;
    }
    while(!que.empty()) que.pop();
    dist[s] = 0;
    que.push(P(0, s));
    while(!que.empty()) {
        P cur = que.top();  que.pop();
        int u = cur.second;
        if(visit[u]) continue;
        visit[u] = 1;
        for(int i = head[u]; i != -1; i = graph[i].next)
        if(!visit[graph[i].to]) {
            int v = graph[i].to;
            if(dist[v] > dist[u]+graph[i].dist) {
                dist[v] = dist[u]+graph[i].dist;
                que.push(P(dist[v], v));
            }
        }
    }
}
void init() {
    for(int i = 0; i <= n*3; i++){
        head[i] = -1;
        layer[i] = 0;
        level[i] = 0;
    }
    totlen = 0;
}
void addEdge(int u, int v, int w) {
    graph[totlen].to = v;
    graph[totlen].next = head[u];
    graph[totlen].dist = w;
    head[u] = totlen++;
}
int main() {
    int _;
    scanf("%d", &_);
    for(int ka = 1; ka <= _; ka++) {
        scanf("%d%d%d", &n, &m, &c);
        init();
        for(int i = 1; i <= n; i++) {
            int ord;
            scanf("%d", &ord);
            level[i] = ord;
            layer[ord]++;
        }
        for(int i = 0; i < m; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            addEdge(u, v, w);
            addEdge(v, u, w);
        }
        // 虚拟点之间连边
        for(int i = 1; i < n; i++) {
            if(layer[i] && layer[i+1]) {
                addEdge(n+i, n+i+1, c);
                addEdge(n+i+1, n+i, c);
            }
        }

        for(int i = 1; i <= n; i++) {
            int lev = level[i];
            // 当前层真实点与虚拟点连边
            addEdge(n+lev, i, 0); 
            // 当前层真实点与下一层虚拟点连边
            if(lev < n) addEdge(i, n+lev+1, c); 
             // 当前层真实点与上一层虚拟点连边
            if(lev > 1) addEdge(i, n+lev-1, c); 
        }
        dijkstra(1);
        printf("Case #%d: %d\n", ka, dist[n] != INF ? dist[n] : -1);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值