• AcWing 920. 最优乘车


    \(AcWing\) \(920\). 最优乘车

    题目传送门

    一、题目描述

    \(H\) 城是一个旅游胜地,每年都有成千上万的人前来观光。

    为方便游客,巴士公司在各个旅游景点及宾馆,饭店等地都设置了巴士站并开通了一些 单程 巴士线路。

    每条单程巴士线路从某个巴士站出发,依次途经若干个巴士站,最终到达终点巴士站。

    一名旅客最近到 \(H\) 城旅游,他很想去 \(S\) 公园游玩,但如果从他所在的饭店没有一路巴士可以直接到达 \(S\) 公园,则他可能要先乘某一路巴士坐几站,再下来换乘同一站台的另一路巴士,这样换乘几次后到达 \(S\) 公园。

    现在用整数 \(1,2,…N\)\(H\) 城的所有的巴士站编号,约定这名旅客所在饭店的巴士站编号为 \(1\)\(S\) 公园巴士站的编号为 \(N\)

    写一个程序,帮助这名旅客寻找一个 最优乘车方案,使他在从饭店乘车到 \(S\) 公园的过程中 换乘的次数最少

    输入格式
    第一行有两个数字 \(M\)\(N\),表示开通了 \(M\) 条单程巴士线路,总共有 \(N\) 个车站。

    从第二行到第 \(M+1\) 行依次给出了第 \(1\) 条到第 \(M\) 条巴士线路的信息,其中第 \(i+1\) 行给出的是第 \(i\) 条巴士线路的信息,从左至右按运行顺序依次给出了该线路上的所有站号,相邻两个站号之间用一个空格隔开。

    输出格式
    共一行,如果无法乘巴士从饭店到达 \(S\) 公园,则输出 \(NO\),否则输出 最少换乘次数,换乘次数为 \(0\) 表示不需换车即可到达。

    二、处理输入问题

    本题的 输入比较特殊,每一条的路线,没有说明走了几个站点,只是说一行结束时,此路线结束。

    此时,需要小心应对,我总结了三种办法:

    1、\(scanf+getchar\)

    // 1.第一个肯定有
    scanf("%d", &stop[++cnt]);
    
    // 2、下一个输入
    // (1)换行键 ASCII 10
    // (2)空格键 ASCII 32
    // (3)最后一个输入结束 -1(EOF)
    char ch = getchar();
    
    // 3、如果读入的字符是空格,说明后面还有数字要读
    while (ch == ' ') {
        // while (ch != EOF && ch != 10) { //这样写,与 ch==' '是等价的
        scanf("%d", &stop[++cnt]); //还有就继续读
        ch = getchar();            //为下一次做准备
    }
    

    此方法个人认为最清晰,最理想,需要记忆,推荐。

    2、\(cin+stringstream\)

    cin >> m >> n;
    string line;
    getchar();                   //读掉换行
    while (m--) {                // m条边
        getline(cin, line);      //读入一行
        stringstream ssin(line); //以流的形式ssin这一行,因为每一行不知道有多少个站点,不知道什么时候结束
        int cnt = 0, p;
        while (ssin >> p) stop[cnt++] = p; //记录一共几个站点
        ...
    }
    

    此方法需要读入第一个换行符,还引入了新的\(stringstream\),繁琐,不推荐。

    3、\(cin+getchar\)

    cin >> m >> n;
    while (m--) { // m条边
        int cnt = 0;
        //第一个肯定有
        cin >> stop[++cnt];
        //第二个不一定有
        char ch = getchar();
        while (ch == ' ') {
            cin >> stop[++cnt];
            ch = getchar(); //为下一次做准备
        }
        ...
    }
    

    此方法不好!原因是我总喜欢在使用\(cin\)时,采用下面的代码进行读入加速:

    cin.tie(0), ios::sync_with_stdio(false);
    

    注意!!!:cin 解锁使用时,不能与 scanfgetchar, cin.getline( ) 混用,一定要注意,会出错。

    相关链接说明

    二、算法分析

    换乘多少次 可以转换为 乘坐过多少次车减\(1\)

    • 在同一条路线中,任意一个在此路线上的车站均能沿着该路线的方向到达后面的车站,权值都是\(1\),表示只乘坐一次车

    • 通过建图,由于权值均是\(1\),使用\(bfs\)求出\(1\)号点到\(n\)号点最少乘过多少次车

    三、邻接矩阵+\(BFS\)

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    using namespace std;
    
    const int N = 510;
    
    struct Node {
        int id;
        int step;
    };
    
    int n;       //总共有N个车站
    int m;       //开通了M条单程巴士线路
    int g[N][N]; //邻接矩阵装图
    int stop[N]; //站点数组
    
    int bfs() {
        queue<Node> q;  //队列,哪个节点,第几步
        q.push({1, 0}); //饭店的巴士站编号为 1,自己到自己的站数是0
    
        while (q.size()) {
            auto t = q.front();
            q.pop();
    
            if (t.id == n) return t.step; //找到
    
            for (int i = 1; i <= n; i++) {   //枚举每个路线,邻接矩阵这样枚举似乎有点垃圾啊,不管有没有路,全扫描一遍?
                if (g[t.id][i]) {            //如果当前点到i点有边,并且,没有走过
                    q.push({i, t.step + 1}); //走到i点,由i再次向四周覆盖
                    g[t.id][i] = 0;          //走过的标识已走过,防止重复走
                }
            }
        }
        //如果上面结束,也没有返回,说明上面的代码中无法成功走到n点
        return -1;
    }
    
    int main() {
        scanf("%d%d", &m, &n); //总共有N个车站,开通了M条单程巴士线路
        while (m--) {                // m条边
            int cnt = 0;
            // 1.第一个肯定有
            scanf("%d", &stop[++cnt]);
    
            // 2、下一个输入
            // (1)换行键 ASCII 10
            // (2)空格键 ASCII 32
            // (3)最后一个输入结束 -1(EOF)
            char ch = getchar();
    
            // 3、如果读入的字符是空格,说明后面还有数字要读
            while (ch == ' ') {
                // while (ch != EOF && ch != 10) { //这样写,与 ch==' '是等价的
                scanf("%d", &stop[++cnt]); //还有就继续读
                ch = getchar();            //为下一次做准备
            }
    
            //每个汽车的前站达到后站的距离都为1 因为是同一辆车 未换车
            for (int i = 1; i <= cnt; i++)
                for (int j = i + 1; j <= cnt; j++)
                    g[stop[i]][stop[j]] = 1; //记录两个站点间有一条公交线,权值为1
        }
    
        //因为边权为1,所以可以使用bfs
        int res = bfs();
        if (res == -1)
            puts("NO");
        else
            //使他在从饭店乘车到 S 公园的过程中换乘的次数最少
            //换乘次数,比如 A->B->C->D ,乘车次数3次,换乘次数2次,因为第1次不算,第2次算换了一次,第3次算换了两次
            printf("%d\n", res - 1);
    
        return 0;
    }
    

    四、\(Dijkstra\)

    #include <bits/stdc++.h>
    
    using namespace std;
    const int INF = 0x3f3f3f3f;
    typedef pair<int, int> PII;
    
    const int N = 1e5 + 10;
    const int M = 2 * N;
    
    int n; //总共有N个车站
    int m; //开通了M条单程巴士线路
    int h[N], e[M], w[M], ne[M], idx;
    int dist[N]; //最小距离数组
    int stop[N]; //站点数组
    bool st[N];  //是否在队列中
    
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
    }
    // 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
    void dijkstra() {
        memset(dist, 0x3f, sizeof dist);                  //求最小设最大
        dist[1] = 0;                                      // 1到自己,乘车数0
        priority_queue<PII, vector<PII>, greater<PII>> q; //小顶堆
        q.push({0, 1});                                   // 1号入队列
    
        while (q.size()) {
            auto t = q.top();
            q.pop();
            int u = t.second;
            int d = t.first; //此处 d=t.first没有用上,经测试,其实d=dist[u],用哪个都是一样的
            if (st[u]) continue;
            st[u] = true;
            for (int i = h[u]; ~i; i = ne[i]) {
                int j = e[i];
                if (dist[j] > dist[u] + w[i]) {
                    dist[j] = dist[u] + w[i];
                    q.push({dist[j], j});
                }
            }
        }
    }
    
    int main() {
        memset(h, -1, sizeof h); //初始化邻接表
        scanf("%d%d", &m, &n);   //总共有N个车站,开通了M条单程巴士线路
        while (m--) {            // m条边
            int cnt = 0;
            scanf("%d", &stop[++cnt]);
            char ch = getchar();
            while (ch == ' ') {
                scanf("%d", &stop[++cnt]); //还有就继续读
                ch = getchar();            //为下一次做准备
            }
            for (int j = 1; j <= cnt; j++)
                for (int k = j + 1; k <= cnt; k++)
                    add(stop[j], stop[k], 1);
        }
    
        dijkstra();
        if (dist[n] == INF)
            puts("NO");
        else
            printf("%d\n", dist[n] - 1);
        return 0;
    }
    

    五、\(SPFA\)

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    
    using namespace std;
    const int INF = 0x3f3f3f3f;
    typedef pair<int, int> PII;
    
    const int N = 1e5 + 10;
    const int M = 2 * N;
    
    int n; //总共有N个车站
    int m; //开通了M条单程巴士线路
    int h[N], e[M], w[M], ne[M], idx;
    int dist[N]; //最小距离数组
    int stop[N]; //站点数组
    bool st[N];  //是否在队列中
    
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
    }
    
    //从start出发
    void spfa(int S) {
        memset(dist, 0x3f, sizeof dist);
        dist[S] = 0;
    
        queue<int> q;
        q.push(S);
        st[S] = true;
    
        while (q.size()) {
            int u = q.front();
            q.pop();
            st[u] = false;
    
            for (int i = h[u]; ~i; i = ne[i]) {
                int j = e[i];
                if (dist[j] > dist[u] + w[i]) {
                    dist[j] = dist[u] + w[i];
                    if (!st[j]) {
                        st[j] = true;
                        q.push(j);
                    }
                }
            }
        }
    }
    
    int main() {
        memset(h, -1, sizeof h); //初始化邻接表
        scanf("%d%d", &m, &n);   //总共有N个车站,开通了M条单程巴士线路
        while (m--) {            // m条边
            int cnt = 0;
            scanf("%d", &stop[++cnt]);
            char ch = getchar();
            while (ch == ' ') {
                scanf("%d", &stop[++cnt]); //还有就继续读
                ch = getchar();            //为下一次做准备
            }
            for (int j = 1; j <= cnt; j++)
                for (int k = j + 1; k <= cnt; k++)
                    add(stop[j], stop[k], 1);
        }
    
        spfa(1);
        if (dist[n] == INF)
            puts("NO");
        else
            printf("%d\n", dist[n] - 1);
        return 0;
    }
    

    六、\(Floyd\)

    #include <iostream>
    #include <cstring>
    #include <sstream>
    using namespace std;
    const int N = 510;
    int n, m, dist[N][N];
    int stop[N];
    
    // AC 905 ms
    int main() {
        memset(dist, 0x3f, sizeof dist);
    
        scanf("%d%d", &m, &n);
        while (m--) {
            int cnt = 0;
            scanf("%d", &stop[++cnt]);
            char ch = getchar();
            while (ch == ' ') {
                scanf("%d", &stop[++cnt]);
                ch = getchar();
            }
            //单程巴士
            //每个汽车的前站达到后站的距离都为1 因为是同一辆车 未换车
            for (int i = 1; i <= cnt; i++)
                for (int j = i + 1; j <= cnt; j++)
                    dist[stop[i]][stop[j]] = 1;
        }
        // Floyd
        for (int k = 1; k <= n; k++)
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= n; j++)
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
    
        if (dist[1][n] >= 0x3f3f3f3f)
            puts("NO");
        else
            printf("%d\n", dist[1][n] - 1);
        return 0;
    }
    
  • 相关阅读:
    git的操作流程命令步骤 软件测试媛
    IT相关的编程技术类学习网站整理 软件测试媛
    Linux中的centos下使用docker搭建gitlab步骤 软件测试媛
    Windows下安装MSI格式的MySQL8.0,且使用自定义配置安装步骤 软件测试媛
    MySQL远程连接工具之Navicat Premium下载及安装步骤 软件测试媛
    截止2021年底,我国18个税种中已有12个税种完成立法
    国产银河麒麟Kylin V10操作系统如何把常用文件夹加入左侧侧边栏(类似windows文件资源管理器中的收藏夹)
    消费税
    做任何事(决策)之前都要先考虑成本,再考虑收益
    具名组匹配(ES9)更改时间格式
  • 原文地址:https://www.cnblogs.com/littlehb/p/16004859.html
Copyright © 2020-2023  润新知