ZOJ Problem Set - 1456

Minimum Transport Cost

Time Limit: 2 Seconds       Memory Limit: 65536 KB

These are N cities in Spring country. Between each pair of cities there may be one transportation track or none. Now there is some cargo that should be delivered from one city to another. The transportation fee consists of two parts:

The cost of the transportation on the path between these cities, and

a certain tax which will be charged whenever any cargo passing through one city, except for the source and the destination cities.

You must write a program to find the route which has the minimum cost.


Input

First is N, number of cities. N = 0 indicates the end of input.

The data of path cost, city tax, source and destination cities are given in the input, which is of the form:

a11 a12 ... a1N
a21 a22 ... a2N
...............
aN1 aN2 ... aNN
b1  b2  ... bN

c d
e f
...
g h

where aij is the transport cost from city i to city j, aij = -1 indicates there is no direct path between city i and city j. bi represents the tax of passing through city i. And the cargo is to be delivered from city c to city d, city e to city f, ..., and g = h = -1. You must output the sequence of cities passed by and the total cost which is of the form:


Output

From c to d :
Path: c-->c1-->......-->ck-->d
Total cost : ......
......

From e to f :
Path: e-->e1-->..........-->ek-->f
Total cost : ......

Note: if there are more minimal paths, output the lexically smallest one. Print a blank line after each test case.


Sample Input

5
0 3 22 -1 4
3 0 5 -1 -1
22 5 0 9 20
-1 -1 9 0 4
4 -1 20 4 0
5 17 8 3 1
1 3
3 5
2 4
-1 -1
0


Sample Output

From 1 to 3 :
Path: 1-->5-->4-->3
Total cost : 21

From 3 to 5 :
Path: 3-->4-->5
Total cost : 16

From 2 to 4 :
Path: 2-->1-->5-->4
Total cost : 17


这道题目跪的舒服啊,WA了无数次始终坚信自己代码没写错,在没有测试数据和网上找不到相同解法的情况,被活活折磨死了。碰巧某堂课上在纸上画图,发现自己的算法是错误的。
这道题是求最短路的字典序最小解,一开始以为bellman-ford算法找到第一条路径就是字典序最小解,其实不是这样的,由于bellman-ford的松弛是按照距离源点的边的条数不断增加的方式来实施(话虽这么说,其实可以发现真正的代码实现并非如此(借助本次更新值得),但也确实是正确的(原理只能参看神书《算导》)),那么字典序小的解可能会因为距离源点的边数太多,被松弛相当多次后而得到,也就是第一次的得到最优解并非是字典序最小的解。
既然这个方法不行,那么就换dijkstra算法,仔细琢磨后发现dijkstra算找到的第一条路径也并非字典序最小的解,至于原因吗?如果你对算法的原理十分了解,那么可以随意构造出一组测试数据,让第一条路径并非是
字典序最小的。
那该怎么办?最后经过各种尝试,想到一种怪异方法。首先利用单源最短路算法,得到源点到所有点的最短路。哦,这里不可以这样简单,我们需要反向建图,源点和终点互换,得到终点到所有点的最短路。
然后尝试向两个点之间插入点,来构造字典序最小解,可以发现当插入点(不等于当前起点)可以插入(也就是满足adj+dist==dist具体看代码),那么选择插入,这样能保证解的字典序更小,一直到插入点序号等于终点时,算法终止,之后不要再插入点了,否则字典序将增大。

代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#define Maxn 200
using namespace std;

int dist[Maxn],pre[Maxn],shortest[Maxn],adj[Maxn][Maxn],t[Maxn];
const int inf=10000000;
void bellman(int u,int n){
    for(int i=1;i<=n;i++)
        dist[i]=inf;
    dist[u]=0,pre[u]=0;
    for(int i=1;i<n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                if(adj[k][j]!=-1&&dist[k]+adj[k][j]<dist[j])
                    dist[j]=dist[k]+adj[k][j],pre[j]=k;
}
int main()
{
    int n,s,e;
    while(scanf("%d",&n),n){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                scanf("%d",&adj[j][i]);
        for(int i=1;i<=n;i++){
            scanf("%d",t+i);
            for(int j=1;j<=n;j++)
                if(adj[i][j]>0)
                    adj[i][j]+=t[i];
        }
        while(scanf("%d%d",&s,&e),s!=-1){
            bellman(e,n);
            printf("From %d to %d :\nPath: ",s,e);
            int tot=0,fr=s,to=e,k;
            shortest[tot++]=fr;
            while(fr!=to){
                for(k=1;k<=n;k++)
                    if(k!=fr&&adj[k][fr]!=-1&&adj[k][fr]+dist[k]==dist[fr]) break;
                shortest[tot++]=k;
                fr=k;
            }
            printf("%d",shortest[0]);
            for(int i=1;i<tot;i++)
                printf("-->%d",shortest[i]);
            printf("\nTotal cost : %d\n\n",s==e?0:dist[s]-t[e]);
        }
    }
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值