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

来源:互联网 发布:在线录像软件 编辑:程序博客网 时间:2024/05/29 10:11

题目链接: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;}
阅读全文
0 0
原创粉丝点击