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

  • 相关阅读:
    右值全部内容
    右值和forward
    alg data以这本
    代码学习only3
    cpp未结
    接下来要处理
    两个进程通过共享内存同步,控制播放器关闭
    接下来
    选择修改还是扩展?
    loadrunner 无法保存许可信息
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2757896.html
Copyright © 2020-2023  润新知