hdu 1385 Minimum Transport Cost

最短路入门经典题

题意是输入n表示n个点从1到n标号,n=0结束程序

然后给出n*n的邻接矩阵,g[i][j]=-1表示i->j没有通路

然后有多个查询,输入u,v,输出u->v的最短路并且打印字典序最小的路径,查询以-1 -1结束

//除了边的权值之外每个点还附带一个权值,所以在松弛操作的时候要把点的权值也计算进去
//另外在总费用最小的情况下要输出字典序最小的路径,同样是在松弛操作那里处理
//如果能更新d[i]使d[i]变小则直接更新
//如果是与d[i]相同则判断一下如果更新的话会不会使路径的字典序更小,如果能才更新否则不更新
//因为由多个查询,显然是用Floy来处理更好,当然也可以写一个对所有源点求最短路的dij
//分别实现

Floy实现

//用Floy实现
#include <stdio.h>
#include <string.h>
#define N 110
#define INF 1000000000
int d[N][N],path[N][N],c[N];
int n,cost;
int s,t;

void input()
{
    int i,j,w;
    for(i=1; i<=n; i++)
        for(j=1; j<=n; j++)
        {
            scanf("%d",&d[i][j]);
            if(d[i][j]==-1) d[i][j]=INF;
            path[i][j]=j;
        }
    for(i=1; i<=n; i++) 
        scanf("%d",&c[i]);

    return ;
}

void Floy()
{
    int i,j,k;

    for(k=1; k<=n; k++)  //中转站k
        for(i=1; i<=n; i++) //起点和终点i,j
            for(j=1; j<=n; j++)
            {
                if( d[i][j] > d[i][k]+d[k][j]+c[k] )
                {
                    d[i][j]=d[i][k]+d[k][j]+c[k];
                    path[i][j]=path[i][k];
                }
                
                else if( d[i][j] == d[i][k]+d[k][j]+c[k] )
                {
                    if(   path[i][j] > path[i][k])
                    {
                        d[i][j]=d[i][k]+d[k][j]+c[k];
                        path[i][j]=path[i][k];
                    }
                }
                
            }
    return ;
}

void print_path(int u , int v) //u是起点v是终点
{
    int k;
    if(u==v)
    {
        printf("%d",v);
        return ;
    }
    k=path[u][v];
    printf("%d-->",u);
    print_path(k,v);
}
int main()
{
    while(scanf("%d",&n)!=EOF && n)
    {
        input();
        Floy();
        
        while(scanf("%d%d",&s,&t))
        {
            if( s==-1 && t==-1) break;
            cost=d[s][t];
            if(s==t)  //起点和终点相同
            {
                printf("From %d to %d :\n",s,t);
                printf("Path: %d\n",s);
                printf("Total cost : %d\n\n",cost);
                continue;
            }
            printf("From %d to %d :\n",s,t);
            printf("Path: ");
            print_path(s,t);
            printf("\n");
            printf("Total cost : %d\n\n",cost);
        }
    }
    return 0;
}

 

 

 SPFA实现

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
#define N 110
#define INF 1000000000
queue <int> q;
bool vis[N];
int g[N][N],c[N];
int path[N][N],d[N][N];
int n;

int chang(int s , int v ,int u)
{
    int p1[N],p2[N],tmp,len1,len2;
    len1=len2=0;
    memset(p1,0,sizeof(p1));
    memset(p2,0,sizeof(p2));

    p1[len1]=v;
    tmp=path[s][v];
    while(tmp!=s)
    {
        p1[++len1]=tmp;
        tmp=path[s][tmp];
    }
    p1[++len1]=tmp;
    
    p2[len2]=v;
    tmp=u;
    while(tmp!=s)
    {
        p2[++len2]=tmp;
        tmp=path[s][tmp];
    }
    p2[++len2]=tmp;

    while(p1[len1]==p2[len2]) 
    { len1--; len2--; }

    return p2[len2]<p1[len1];
}
void spfa(int s)
{
    for(int i=1; i<=n; i++)  //初始化
    {
        d[s][i]=g[s][i];
        path[s][i]=s;
        vis[i]=0;
    }
    d[s][s]=0;
    while(!q.empty()) q.pop();
    for(int i=1; i<=n; i++)
        if(d[s][i]!=INF)
        {
            q.push(i);
            vis[i]=1;
        }

    while(!q.empty())
    {
        int u;
        u=q.front();  //读取队头元素
        q.pop();     //队头元素出队
        vis[u]=0;    //消除标记

        for(int v=1; v<=n; v++)  //对所有与u相连的点v进行松弛操作
            if( d[s][u]+g[u][v]+c[u] < d[s][v])         //可以更新路径
            {
                d[s][v]=d[s][u]+g[u][v]+c[u];
                path[s][v]=u;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
            else if( d[s][u]+g[u][v]+c[u] == d[s][v])  //不能更新估计值但是有可能能改变路径
            {
                if( chang(s,v,u) )
                    path[s][v]=u;
            }
    }

    return ;
}

void print_path(int s ,int t)
{
    int u;
    if(t==s)
    {
        printf("%d",s);
        return ;
    }
    u=path[s][t];
    print_path(s,u);
    printf("-->%d",t);
}
int main()
{
    while(scanf("%d",&n)!=EOF && n)
    {
        for(int i=1 ;i<=n; i++)
            for(int j=1; j<=n; j++)
            {
                scanf("%d",&g[i][j]);
                if(g[i][j]==-1)
                    g[i][j]=INF;
            }
        for(int i=1; i<=n; i++)
            scanf("%d",&c[i]);

        for(int i=1; i<=n; i++) //枚举所有源点
            spfa(i);  //对该源点进行单源最短路径
        
        int s,t;
        while(scanf("%d%d",&s,&t)!=EOF)
        {
            if(s==-1 && t==-1) break;
             if(s==t)  //起点和终点相同
            {
                printf("From %d to %d :\n",s,t);
                printf("Path: %d\n",s);
                printf("Total cost : %d\n\n",d[s][t]);
                continue;
            }
            printf("From %d to %d :\n",s,t);
            printf("Path: ");
            print_path(s,t);
            printf("\n");
            printf("Total cost : %d\n\n",d[s][t]);

        }
    }
    return 0;
}

 

Dijkstra实现

//用dij实现
//WA了有5,6次就是因为路径字典序处理不好

#include <stdio.h>
#include <string.h>
#define N 110
#define INF 1000000000
int g[N][N],d[N][N],path[N][N],c[N],cost;
bool cov[N][N];
int n;
int V0,V;

void input()
{
    int i,j;
    for(i=1; i<=n; i++)
        for(j=1; j<=n; j++)
        {
            scanf("%d",&g[i][j]);
            if(g[i][j]==-1) 
                g[i][j]=INF;
        }

    for(i=1; i<=n; i++) scanf("%d",&c[i]);
    return ;
}

int chang(int ss , int jj , int kk)  //比较路径
{
    int p1[N],p2[N],len1,len2,tmp;
    memset(p1,0,sizeof(p1));
    memset(p2,0,sizeof(p2));
    len1=len2=0; 
    p1[len1]=jj;
    len1++;
    tmp=path[ss][jj];
    while(tmp!=ss)
    {
        p1[len1]=tmp;
        len1++;
        tmp=path[ss][tmp];
    }
    p1[len1]=ss;

    p2[len2]=jj;
    len2++;
    tmp=kk;
    while(tmp!=ss)
    {
        p2[len2]=tmp;
        len2++;
        tmp=path[ss][tmp];
    }
    p2[len2]=ss;

    while(p1[len1]==p2[len2])
    { len1--; len2--;}

    return p2[len2] < p1[len1] ;
}

void dij(int s)  //源点是s
{
    int i,j,k,nn,min;

    memset(cov,0,sizeof(cov));  //在这一轮中要清0
    for(i=1; i<=n; i++)  //初始化
    {
        d[s][i]=g[s][i];
        path[s][i]=s;  //初始化路径,所有点的前驱都是源点s
    }
    cov[s][s]=1;
    d[s][s]=0;  //本来应该赋值为0的,不过每个点都附带了权值所以赋初值为c[s]

    for(nn=1; nn<n; nn++)  //nn是个数,还有求出源点到其余n-1个点的最短路
    {
        min=INF; k=s;
        for(i=1; i<=n; i++)
            if( !cov[s][i] && d[s][i] < min)
            {
                min=d[s][i];
                k=i;
            }
        cov[s][k]=1;  //得到了i点的最短路


        for(i=1; i<=n; i++)  if(!cov[s][i])//松弛操作
        {
            if( min+g[k][i]+c[k] < d[s][i]) 
            //如果以k点为准来松弛,要把k点附带的权值算进去
            {
                d[s][i]=d[s][k]+g[k][i]+c[k];
                path[s][i]=k;
            }
            else if( min+g[k][i]+c[k] == d[s][i] )
            {
                if( chang(s , i , k) )  //比较路径成功后才可以替换
                {
                    d[s][i]=d[s][k]+g[k][i]+c[k];
                    path[s][i]=k;
                }
            }
        }

    }

    return ;
}

void print_path(int s , int t)
{
    int k;
    if(t==s)
    {
        printf("%d",s);
        return ;
    }
    k=path[s][t];
    print_path(s,k);
    printf("-->%d",t);

}
int main()
{
    int i,j;
    while(scanf("%d",&n)!=EOF && n)
    {
        input();
        for(V0=1; V0<=n; V0++) //枚举所有的源点用dij去求源点到所有点的最短路
        {
            dij(V0);
            /*
            printf("***********\n");
            printf("D: ");    
            for(i=1; i<=n; i++) printf("%d ",d[V0][i]);    
            printf("\n");
            printf("Path: "); 
            for(i=1; i<=n; i++) printf("%d ",path[V0][i]); 
            printf("\n");
            printf("***********\n");
            */
        }

        
        while(scanf("%d%d",&V0,&V))  //查询
        {
            if(V0==-1 && V==-1) break;
            if(V0==V)
            {
                printf("From %d to %d :\n",V0,V);
                printf("Path: %d\n",V0);
                printf("Total cost : %d\n\n",d[V0][V]);

            }
            else
            {
                printf("From %d to %d :\n",V0,V);
                printf("Path: ");
                print_path(V0,V);
                printf("\n");
                printf("Total cost : %d\n\n",d[V0][V]);
            }
        }
    }
    return 0;
}

由上面的代码可以看到Floy算法实现比Dijkstra算法实现方便很多,代码量少,通俗易懂,细节地方少不易出错,最重要的是这道题输出字典序最小的路径,这个要求正好满足Floy而不太满足Dijkstra,如果非要用Dijkstra实现的在更新路径的时候如果额外判断,实际上时间就增加了

一:关于Dijkstra的初始化也会对这道题有影响,因为字典序最小路径,并且算最后的总费用的时候,起点和终点附带的权值是不能计算进去的,初始化问题将会影响这两个问题的解决。这个问题用第一种初始化才好

//dij算法初始化有两种其实是一样的只是写法不同,但是发现,不同的问题用不同的初始化
//会影响后面的代码,适合的初始化能提高效率并且精简代码提高代码可读性

//初始化1

memset(cov,0,sizeof(cov));
for(i=1; i<=n; i++)
{
    d[s][i]=g[s][i];  //一开始所有点的最短路都看作是和源点直接相连的边的权值
    path[s][i]=s;     //自然所有点的前驱就是源点s包括源点自己
}
cov[s][s]=1;   //源点到源点的最短路不用计算
d[s][s]=0;    //源点到源点的最短路为0

//初始化2

memset(cov,0,sizeof(cov));
for(i=1; i<=n; i++)
{
    d[s][i]=INF;   //所有点的最短路都还没算而且不知道是否存在所有全部初始化为INF
    path[s][i]=0;  //当然也就不知道点i的前驱是谁,点是从1开始标号的所以所有点的前驱都标记为0表示没有
}
//注意这里是没有 cov[s][s]=1; 因为并没有求出源点s的最短路虽然我们知道是不用求的为0
d[s][s]=0;    //源点的最短路不用求已知为0所以赋值为0,这个赋值是为了后面的代码可以运行做准备的
path[s][s]=s; //源点的前驱我们标记为源点,当然也可以保留为0的,在打印路径的时候判断做出些微的改变即可

二:关于Dij和Floy记录路径的问题

在Floy中,输出路径时,path[u][v]=k ,  表示从u到v的路径中经过点k,然后就用点k来替换u,注意是替换u,变为path[k][v] ,直到k=v , 即不断替换起点

所以Floy的路径初始化为path[u][v]=v;   在进行松弛操作的时候更新路径是 path[u][v]=path[u][k];

在Dij中,path[s][t]=k , 也表示从s到t的路径经过点k,然后就用点k替换t,注意是替换t,变为path[s][k],直到s=k , 即不断替换终点

所以这道题在更新字典序最小的路径的时候,不能单单判断一个点,要把整条路径全部拿出来,从源点开始比较直到找到第一个不同的点,也就是代码中的chang()函数

一开始wa了很多次,就是因为判断路径的那句代码写为  

if(path[s][i] > k)  //就更新

这样是不对,因为path[s][i]只是点s到点i的路径中在点i前面的那个点,是靠最后的点,而比较字典序是从头开始比较直到第一个不同的元素为止,如果直接就这样比较相当于是从后面开始比较,是错误的

但是在Floy中就可以直接比较,代码中的比较是  

if(path[i][j] > path[i][k])  //就更新

因为path[i][j]记录的就是 i-->j 路径中就靠点i的点,是最开始的点 ,而path[i][k]也是最靠近点i的点,所有是可以更新的,是符合字典序的比较原则的

而SPFA算法的路径问题和DIJ是一样的,所以两者处理方法一样

原文地址:https://www.cnblogs.com/scau20110726/p/2757896.html