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是一样的,所以两者处理方法一样

你可能感兴趣的:(port)