题目链接: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): 1779Problem 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 33 3 3
1 3 2
1 2 2
2 3 2
1 3 4Sample 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;
}