PAT 1030 Travel Plan(单源最短路径+优化Dijkstra)

题目

https://www.patest.cn/contests/pat-a-practise/1030

题意:给出每座城市之间高速公路的长度和花费,求从给定起点到终点的最短路径并输出,若有多条最短路径,记录花费最小的那条。

解题思路

本题是单源最短路径的简单变形。不仅要考虑距离,还要考虑花费。

关于单源最短路径,详见之前的一篇博客:PAT 1003 Emergency(单源最短路径+Dijkstra)

对算法的修改如下:

  • 在维护dist数组的同时,还要维护cost数组,其意义与dist基本相同。
  • 松弛时要考虑的情况:

    • 通过pos到k的距离更短
    • 通过pos到k的距离相等时,花费更小。

    出现上述两种情况时,要同时更新dist和cost数组。

AC代码

优先队列优化过的Dijkstra

#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;

typedef pair<int, int> P; ///first是距离,second是编号
const int maxn = 505, INF = 1<<27;

int graph[maxn][maxn], weight[maxn][maxn];
int dist[maxn], cost[maxn]; //dist、cost表示当前点距源点最短距离(花费)
bool visit[maxn];
int pre[maxn]; //记录前驱顶点
int n, m, src, dest;

void init() //用fill初始化,比memset功能更强,注意格式
{
    fill(graph[0], graph[0]+maxn*maxn, INF); //二维数组形式
    fill(weight[0], weight[0]+maxn*maxn, INF); //一维数组形式
    fill(visit, visit+maxn, false); //布尔形式
    fill(dist, dist+maxn, INF);
    fill(cost, cost+maxn, INF);
}

void Dijkstra(int s) ///优先队列优化的dijkstra算法
{
    dist[s] = cost[s] = 0;
    priority_queue<P, vector<P>, greater<P> > q; //按照pair的第一个值升序
    q.push(P(0, s));
    while(!q.empty())
    {
        P out = q.top();
        q.pop();
        int pos = out.second; //顶点编号
        if (visit[pos])
            continue;
        //从pos开始松弛
        visit[pos] = true; //标记访问
        for (int i = 0; i < n; ++i)
        {
            if (!visit[i] && dist[pos] + graph[pos][i] <= dist[i]) //通过pos到i路径可能会更短
            {
                if ( dist[pos] + graph[pos][i] < dist[i] ||
                     (dist[pos] + graph[pos][i] == dist[i] && cost[pos] + weight[pos][i] < cost[i]) ) //路径更短或路径长度相等但花费更少
                {
                    pre[i] = pos; //更新前驱顶点
                    dist[i] = dist[pos] + graph[pos][i];
                    cost[i] = cost[pos] + weight[pos][i];
                    q.push(P(dist[i], i)); //加入更新后的路径值和顶点编号
                }
            }
        }
    }
}

int main()
{
    scanf("%d %d %d %d", &n, &m, &src, &dest);
    init(); //初始化
    int from, to, dis, cos;
    for (int i = 0; i < m; ++i)
    {
        scanf("%d %d %d %d", &from, &to, &dis, &cos);
        graph[from][to] = graph[to][from] = dis;
        weight[from][to] = weight[to][from] = cos;
    }
    Dijkstra(src);
    //输出路径
    int path[maxn], cnt = 0, tmp = dest;
    while (tmp != src)
    {
        path[cnt++] = tmp;
        tmp = pre[tmp];
    }
    path[cnt++] = src;
    for (int i = cnt-1; i >= 0; --i)
        printf("%d ", path[i]);
    printf("%d %d\n", dist[dest], cost[dest]);
    return 0;
}

小技巧:
STL中的泛型函数fill功能比memset更强大,memset只能对字节赋值,而fill则是任何值都可以,调用规则:
fill(first,last,val),其中first 为容器的首迭代器,last为容器的末迭代器,last为将要替换的值,注意二维数组a[x][y]的首尾迭代器要写a[0]而不是a。

未优化的Dijkstra

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 505, INF = 0X3f3f3f3f;

int map[maxn][maxn], weight[maxn][maxn];
int dist[maxn], cost[maxn]; //维护dist同时维护cost
int pre[maxn]; //记录前驱结点
bool visited[maxn];
int n;

void init()
{
    for (int i = 0; i<n; ++i)
    {
        for (int j = 0; j<n; ++j)
        {
            map[i][j] = (i==j)? 0:INF;
            weight[i][j] = (i==j)? 0:INF;
        }
        visited[i] = false;
        pre[i] = -1;
    }
}

void dijkstra(int st)
{
    for (int i = 0; i<n; ++i)
    {
        dist[i] = map[st][i];
        cost[i] = weight[st][i]; //更新cost
        if (dist[i] != INF)
            pre[i] = st;
    }
    visited[st] = true;
    for (int i = 0; i < n-1; ++i)
    {
        int pos = -1, minn = INF;
        //找到dist当前最小值
        for (int j = 0; j < n; ++j)
        {
            if (!visited[j] && dist[j] < minn)
            {
                minn = dist[j];
                pos = j;
            }
        }
        visited[pos] = true; //标记pos被访问过
        //松弛操作
        for (int k = 0; k < n; ++k)
        {
            if (!visited[k] && dist[pos] + map[pos][k] <= dist[k])
            {
                //经过pos到k的距离更短,或距离相同代价更小
                if (dist[pos] + map[pos][k] < dist[k] || ((dist[pos] + map[pos][k] ==  dist[k]) && cost[pos] + weight[pos][k] < cost[k]))
                {
                    dist[k] = dist[pos] + map[pos][k]; //更新dist
                    cost[k] = cost[pos] + weight[pos][k]; //更新cost
                    pre[k] = pos;
                }
            }
        }
    }
}


int main()
{
    ios::sync_with_stdio(false);
    int m, start, dest;
    cin >> n >> m >> start >> dest;
    init();
    int t1, t2, d, w;
    for (int i = 0; i < m; ++i)
    {
        cin >> t1 >> t2 >> d >> w;
        map[t1][t2] = map[t2][t1] = d;
        weight[t1][t2] = weight[t2][t1] = w; //记录代价
    }
    dijkstra(start);
    int along[maxn], cnt = 0;
    int t = dest;
    while (t != start) //记录所有前驱结点
    {
        along[cnt++] = t;
        t = pre[t];
    }
    along[cnt++] = start; //加入起点
    for (int i = cnt-1; i >= 0; --i)
        cout << along[i] << ' ';
    cout << dist[dest] << ' ' << cost[dest] << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值