最短路专题(入门)(更新中)

最短路

01 UVA247 Calling Circles

  • 题目链接:UVA247 电话圈 Calling Circles

  • 思路:因为数据比较小,可 O ( n m ) O(nm) O(nm) 遍历查找两点对可到达的有向路径,或 O ( n 3 ) O(n^3) O(n3)Floyd 找到所有有向路径。注意字符串可用 m a p map map 存下是否存在,可用 v e c t o r vector vector 对应编号。最后所有连边在同一集合的一同输出。

    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    #define PII pair<int, int>
    const db Pi = 3.141592653589793;
    const int INF = 0x3f3f3f3f;
    const int N = 1e2 + 5;
    const db eps = 1e-10;
    int n, m, cas = 0;
    int cnt, x, y;
    int f[N][N], vis[N];
    map<string, int> mp;
    vector<string> name;
    void init(){
        mp.clear(), name.clear();
        cnt = -1;
        memset(f, 0x3f, sizeof(f));
        memset(vis, 0, sizeof(vis));
        rep(i, 0, n - 1) f[i][i] = 0;
    }
    void add(string s, int &x){
        if(mp.count(s)) x = mp[s];
        else{
            x = ++cnt;
            mp.insert({s, x});
            name.push_back(s);
        }
    }
    int main(){
        while(cin >> n >> m){
            if(!n && !m) break;
            init();
            //字符串转为对应编号
            rep(i, 1, m){
                string u, v; cin >> u >> v;
                add(u, x), add(v, y);
                f[x][y] = 1;  //建图
            }
            //floyd
            rep(k, 0, n - 1) rep(x, 0, n - 1) rep(y, 0, n - 1)
                f[x][y] = min(f[x][y], f[x][k] + f[k][y]);
            //输出
            printf("Calling circles for data set %d:\n", ++cas);
            rep(u, 0, n - 1){
                if(vis[u]) continue;
                cout << name[u];
                vis[u] = 1;
                rep(v, 0, n - 1){
                    if(vis[v]) continue;
                    if(f[u][v] < INF && f[v][u] < INF){
                        cout << ", " << name[v];
                        vis[v] = 1;
                    }
                }
                cout << endl;
            }
        }
    }
    

02 UVa10048 Audiophobia

  • 题目链接:UVA10048 噪音恐惧症 Audiophobia

  • 题目:无向带权图中,求两点之间道路上的边中最大权值的最小值

  • 思路:一个典型的 Floyd 问题,由于求的是最大边权的最小值,只需要将转移方程改成 f[x][y] = min(f[x][y], max(f[x][k], f[k][y])) 即可

    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    #define PII pair<int, int>
    const db Pi = 3.141592653589793;
    const int INF = 0x3f3f3f3f;
    const int N = 1e2 + 5;
    const db eps = 1e-10;
    int cas = 0, n, m, q;
    int f[N][N];
    int main(){
        while(cin >> n >> m >> q){
            if(!n) break;
            memset(f, 0x3f, sizeof(f));
            rep(i, 1, n) f[i][i] = 0;
            rep(i, 1, m){
                int u, v, val; cin >> u >> v >> val;
                //可能有重边!!!
                f[u][v] = min(f[u][v], val);
                f[v][u] = min(f[v][u], val);
            }
            rep(k, 1, n) rep(x, 1, n) rep(y, 1, n){
                if(f[x][k] != INF && f[k][y] != INF) f[x][y] = min(f[x][y], max(f[x][k], f[k][y]));
            }
            if(cas) printf("\n");
            printf("Case #%d\n", ++cas);
            rep(i, 1, q){
                int u, v; cin >> u >> v;
                if(f[u][v] == INF) cout << "no path" << endl;
                else cout << f[u][v] << endl;
            }
        }
    }
    

03 UVa1001 Say Cheese

  • 题目链接:UVA1001 奶酪里的老鼠 Say Cheese

  • 思路:每个两点之间的距离可理解成直线距离减去两点半径即可,当然也不能为负。这样便是从出发点可经过 n n n 个节点最终到达结束点的最短路问题。注意最后输出时间要 * 10 和四舍五入。

    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    const int N = 1e2 + 5;
    int cas = 0, n;
    db f[N][N];  //注意设成double!!!
    struct AC{
        db x, y, z, r;
    }a[N];
    db dis(AC a, AC b){
        return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z));
    }
    int main(){
        while(cin >> n){
            if(n == -1) break;
            rep(i, 1, n) cin >> a[i].x >> a[i].y >> a[i].z >> a[i].r;
            //设起点为 a[0],终点为 a[n + 1]
            cin >> a[0].x >> a[0].y >> a[0].z >> a[n + 1].x >> a[n + 1].y >> a[n + 1].z;
            a[0].r = a[n + 1].r = 0.0;
            rep(i, 0, n + 1) rep(j, 0, n + 1){
                if(i == j) f[i][j] = 0.0;
                else f[i][j] = max(0.0, dis(a[i], a[j]) - a[i].r - a[j].r);
            }
            rep(k, 0, n + 1) rep(x, 0, n + 1) rep(y, 0, n + 1){  //floyd
                f[x][y] = min(f[x][y], f[x][k] + f[k][y]);
            }
            printf("Cheese %d: Travel time = %.0lf sec\n", ++cas, round(f[0][n + 1] * 10));
        }
    }
    

04 POJ1125 Stockbroker Grapevine

  • 题目链接:Stockbroker Grapevine - POJ 1125

  • 思路:重点看清不成立输出 disjoint 的情况,即存在节点没有连通道路时(既没有输出边,也没有输入边)。此外 f l o y d floyd floyd 搜出每个点到其他点的最短路后,找到到最远点时间最短的即可。

    #include<iostream>
    #include<cmath>
    #include<cstring>
    #include<set>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    #define PII pair<int, int>
    const db Pi = 3.141592653589793;
    const int INF = 0x3f3f3f3f;
    const int N = 1e2 + 5;
    const db eps = 1e-10;
    int n, f[N][N], ans[N], ok;
    set<int> st;
    void init(){
        memset(f, 0x3f, sizeof(f));
        memset(ans, 0, sizeof(ans));
        st.clear();
        rep(i, 1, n) f[i][i] = 0;
    }
    int main(){
        while(cin >> n){
            if(!n) break;
            init();
            rep(u, 1, n){
                int t; cin >> t;
                if(t) st.insert(t);
                rep(i, 1, t){
                    int v, val; cin >> v >> val;
                    st.insert(v);
                    f[u][v] = val;
                }
            }
            if(st.size() < n){
                puts("disjoint");
                continue;
            }
            rep(k, 1, n) rep(x, 1, n) rep(y, 1, n){  //floyd
                if(f[x][k] != INF && f[k][y] != INF) f[x][y] = min(f[x][y], f[x][k] + f[k][y]);
            }
            rep(x, 1, n) rep(y, 1, n) ans[x] = max(ans[x], f[x][y]);
            int minn = INF, node = 0;
            rep(i, 1, n){
                if(minn > ans[i]){
                    minn = ans[i];
                    node = i;
                }
            }
            cout << node << " " << minn << endl;
        }
    }
    

05 USACO09Oct Heat Wave G

  • 题目链接:P1339 USACO09OCT Heat Wave G

  • 思路:普通求最短路,直接 d i j k s t r a dijkstra dijkstra 即可。注意是无向图

    //dijkstra堆优化
    #include<bits/stdc++.h>
    #define rep(i, a, b) for(int i = a; i <= b; i++)
    #define per(i, a, b) for(int i = a; i >= b; i--)
    const int INF = 0x7fffffff;
    #define ll long long
    #define PII pair<int, int>
    const int N = 3e4 + 5;  //应该记为边数
    using namespace std;
    int n, m, s, t;
    int vis[N], dist[N];
    vector<PII> vec[N];
    void dijkstra(){
        priority_queue<PII, vector<PII>, greater<PII> > heap;
        dist[s] = 0, heap.push({0, s});
        while(heap.size()){
            PII now = heap.top(); heap.pop();
            int nod = now.second;
            if(vis[nod]) continue;
            vis[nod] = 1;
            for(auto j : vec[nod]){
                int To = j.first, distmp = j.second;
                if(dist[To] > now.first + distmp){
                    dist[To] = now.first + distmp;
                    heap.push({dist[To], To});
                }
            }
        }
        return;
    }
    int main(){
        cin >> n >> m >> s >> t;
        memset(vis, 0, sizeof(vis));
        rep(i, 1, m){
            int u, v, w; cin >> u >> v >> w;
            //注意是无向图
            vec[u].push_back({v, w});
            vec[v].push_back({u, w});
        }
        memset(dist, 0x3f, sizeof(dist));
        dijkstra();
        cout << dist[t] << endl;
    }
    

06 POJ1847 Tram

  • 题目链接:Tram - POJ 1847

  • 思路:普通最短路,只不过边权为 0 0 0 1 1 1,而且数据太小,可以直接同 f l o y d floyd floyd

    #include<iostream>
    #include<cmath>
    #include<cstring>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define PII pair<int, int>
    const int INF = 0x3f3f3f3f;
    const int N = 1e2 + 5;
    int n, s, t, f[N][N];
    int main(){
        cin >> n >> s >> t;
        memset(f, 0x3f, sizeof(f));
        rep(u, 1, n){
            int k; cin >> k;
            rep(i, 1, k){
                int v; cin >> v;
                f[u][v] = (i == 1 ? 0 : 1);
            }
            f[u][u] = 0;
        }
        rep(k, 1, n) rep(x, 1, n) rep(y, 1 ,n){  //floyd
            f[x][y] = min(f[x][y], f[x][k] + f[k][y]);
        }
        if(f[s][t] == INF) puts("-1");
        else cout << f[s][t] << endl;
    }
    

07 POJ3255 Roadblocks

  • 题目链接:Roadblocks - POJ 3255

  • 题目无向图 1 ∼ n 1\sim n 1n次最短路(第二短的路)

    • 思路:求次最短路,需要每次更新时,一同更新记录此最短路(如果有的话)。这里用 d i j k s t r a dijkstra dijkstra 堆优化在判断当前更新 tmpmindis 时,若小于最短路 fdist[To] 则更新最短路,并将之前的最短路换为 tmpmindis (即 swap(fdist[To], tmpmindis););此时若 tmpmindis 大于最短路但小于次最短路,则更新次最短路。
    • 原理:到 T o To To 点的次最短路是除最短路外一切路径的第二短
    //求次最短路
    #include<iostream>
    #include<vector>
    #include<queue>
    #define rep(i, a, b) for(int i = a; i <= b; i++)
    #define per(i, a, b) for(int i = a; i >= b; i--)
    const int INF = 0x7fffffff;
    #define ll long long
    #define PII pair<int, int>
    const int N =1e5 + 5;  //应该记为边数
    using namespace std;
    int n, m, s;
    int vis[N], fdist[N], sdist[N];
    vector<PII> vec[N];
    priority_queue<PII, vector<PII>, greater<PII> > heap;
    void dijkstra(){
        fdist[1] = 0, heap.push({0, 1});
        while(heap.size()){
            PII now = heap.top(); heap.pop();
            int nod = now.second;
            for(auto j : vec[nod]){
                int To = j.first, tmpmindis = j.second + now.first;
                if(fdist[To] > tmpmindis){
                    swap(fdist[To], tmpmindis);  //更新最短路和当前次短路!!!
                    heap.push({fdist[To], To});
                }
                if(fdist[To] < tmpmindis && tmpmindis < sdist[To]){
                    sdist[To] = tmpmindis;
                    heap.push({sdist[To], To});
                }
            }
        }
        return;
    }
    int main(){
        scanf("%d%d", &n, &m);
        rep(i, 1, m){
            int x, y, z; scanf("%d%d%d", &x, &y, &z);
            vec[x].push_back({y, z});
            vec[y].push_back({x, z});
        }
        rep(i, 1, n) fdist[i] = sdist[i] = INF;
        dijkstra();
        cout << sdist[n] << endl;
    }
    /*
    in
    4 5
    1 2 100
    2 4 200
    2 3 100
    3 4 100
    1 4 301
    out
    301
    */
    

08 UVA10389 Subway

  • 题目链接:UVA10389 Subway

  • 思路:这道题难点在于处理输入数据,由于没有说明节点数量,只能通过整行输入来判定一组数据读取完成。用 stringstream 来处理整行字符串即可。

    • 问题处理很简单,一条线路上的相邻点之间是地铁速度,其他各点之间是走路速度,建立一个完全图在用 f l o y d floyd floyd 求起点到终点的最短路即可。
    //注意构建边权
    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    #define PII pair<int, int>
    #define PDI pair<db, int>
    #define subway (40000.0 / 60.0)
    #define walk (10000.0 / 60.0)
    const db Pi = 3.141592653589793;
    const int INF = 0x3f3f3f3f;
    const int N = 2e2 + 5;
    const db eps = 1e-10;
    int cas, sx, sy, dx, dy;
    int cnt, line;
    db g[N][N];
    struct AC{
        db x, y;
    }node[N];
    void add(int nx, int ny){
        node[++cnt].x = nx, node[cnt].y = ny;
    }
    db dis(AC a, AC b){
        return sqrt(pow((a.x - b.x), 2) + pow((a.y - b.y), 2));
    }
    int main(){
        cin >> cas;
        while(cas--){
            cnt = 0;
            memset(g, 0, sizeof(g));
            cin >> sx >> sy >> dx >> dy;
            add(sx, sy), add(dx, dy);
            string line;
            getline(cin, line);
            while(getline(cin, line)){
                if(line.empty()) break;  //读到空行,说明结束
                stringstream ss(line);
                int x, y;
                ss >> x >> y;
                add(x, y);
                while(ss >> x >> y){
                    if(x == -1 && y == -1) break;
                    add(x, y);
                    g[cnt - 1][cnt] = g[cnt][cnt - 1] = dis(node[cnt - 1], node[cnt]) / subway;
                }
            }
            rep(i, 1, cnt) rep(j, i + 1, cnt){  //计算走路距离
                if(!g[i][j]) g[i][j] = g[j][i] = dis(node[i], node[j]) / walk;
            }
            rep(k, 1, cnt) rep(i, 1, cnt) rep(j, 1, cnt){  //floyd
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
            }
            printf("%d\n", (int)round(g[1][2]));
            if(cas) printf("\n");
        }
    }
    

09 POJ3662 Telephone Lines

  • 题目链接:Telephone Lines - POJ 3662

  • 题目:在无向图上求一条 1 1 1 n n n 的路径,使路上第 k + 1 k + 1 k+1 大的边的边权最小

  • 思路:由单调性和求最小值最小,想到二分答案。考虑是否存在一种合法的升级方案,使花费不超过 m i d mid mid,然后二分答案寻找尽量小的花费即可。

    • 下面考虑怎样才表示方案合法:判断从 1 1 1 n n n 的所经过路径的最大边权是否不超过 k k k 即可
    • 那么怎么判断合法:要找在 m i d mid mid 为最大值时成立的最短路,可以将边权 > mid 的边设为 1 1 1,最后最多只能用 k k k 个 ; <= mid 的边设为 0 0 0,表示可以随便用。跑一边 d i j k s t r a dijkstra dijkstra 就好啦。
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<queue>
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define PII pair<int, int>
    #define pi acos(-1.0)
    const int INF = 0x3f3f3f3f;
    const int N = 1e3 + 5;
    const db eps = 1e-10;
    using namespace std;
    int n, m, k, g[N][N], maxn;
    vector<PII> eg[N];
    int dist[N], vis[N];
    int dij(int x){  //假设规定最大边权为 x
        rep(i, 1, n){  //预处理新边权
            eg[i].clear();  //清空
            rep(j, 1, n){
                if(g[i][j] == 0) continue;
                if(g[i][j] > x) eg[i].push_back({j, 1});  //边权超限视为带权边,最多只能用 k 个
                else eg[i].push_back({j, 0});  //边权未超限视为无权边,可以随便用
            }
        }
        priority_queue<PII, vector<PII>, greater<PII> > heap;
        memset(dist, 0x3f, sizeof(dist));
        memset(vis, 0, sizeof(vis));
        dist[1] = 0, heap.push({0, 1});  //加入初始节点
        while(heap.size()){
            PII now = heap.top(); heap.pop();
            int nod = now.second;
            if(vis[nod]) continue;
            vis[nod] = 1;
            for(int j = 0; j < eg[nod].size(); j++){
                int To = eg[nod][j].first, distmp = eg[nod][j].second;
                if(dist[To] > now.first + distmp){
                    dist[To] = now.first + distmp;
                    heap.push({dist[To], To});
                }
            }
        }
        if(dist[n] == INF) return (!x ? 2 : 0);  //到不了
        return dist[n] <= k;  //检查边数是否够用
    }
    int main(){
        cin >> n >> m >> k;
        memset(g, 0, sizeof(g));
        rep(i, 1, m){
            int u, v, w; cin >> u >> v >> w;
            g[u][v] = w, g[v][u] = w;
            maxn = max(w, maxn);  //存下最大值
        }
        if(dij(0) == 2){  //试试不花钱行不行
            puts("-1");
            return 0;
        }
        int l = 0, r = maxn, mid;
        while(l < r){  //二分寻找尽量小的花费,使升级方案合法(dist[n] <= k)
            mid = (l + r) / 2;
            if(dij(mid)) r = mid;  //为了尽量小,若合法将 mid 缩小
            else l = mid + 1;
        }
        cout << l << endl;
    }
    

10 UVA658 It’s not a Bug, It’s a Feature!

11 UVA11374 Airport Express

  • 题目链接:UVA11374 Airport Express

  • 题目:在基础无向图上存在一些边权更小的快速边,要求最多经过一条快速边,求起点到终点的最短路。列出最短路路径并说明是否用到快速边,若用到请指明快速边起点。

  • 思路:由于无法确定经过哪条快速边合适,可用 d i j k s t r a dijkstra dijkstra 先求出起点和终点到所有点的最短路。

    • 若使用快速边 ( u , v , w ) (u,v,w) (u,v,w),所需答案便是 startdist[u] + w + enddist[v] ,即起点到达 u u u,经过快速边后, v v v 再到达终点。
    • 判断使用快速边条件:当不用快速边无法从起点到达终点时;或使用快速边可缩短路程时。
    • 列出路径:在用 d i j k s t r a dijkstra dijkstra 求最短路时,可记录到每个点的最短路上的前一个节点。这样当需要列出 ( s t a r t , e n d ) (start,end) (start,end) 的最短路时,只需要遍历一遍 e n d end end 的前每个节点,再输出。注意由于从终点出发的最短路需要以终点结尾,因此路径需要反转。
    #include<bits/stdc++.h>
    #define rep(i, a, b) for(int i = a; i <= b; i++)
    #define per(i, a, b) for(int i = a; i >= b; i--)
    const int INF = 0x7fffffff;
    #define ll long long
    #define PII pair<int, int>
    const int N =5e2 + 5;
    using namespace std;
    int cas = 0, n, s, d, m, k;
    int change, x, y, z;
    ll minn, transferstart, transferend, transferval;
    ll vis[N], startdist[N], enddist[N], startpath[N], endpath[N];
    vector<PII> vec[N];
    void init(){
        change = 0, transferstart = -1, transferend = -1;
        rep(i, 1, n){
            vec[i].clear();
            startdist[i] = enddist[i] = INF;
            startpath[i] = endpath[i] = -1;
        }
    }
    void dijkstra(int s, ll dist[], ll path[]){
        memset(vis, 0, sizeof(vis));
        priority_queue<PII, vector<PII>, greater<PII> > heap;
        dist[s] = 0, heap.push({0, s});
        while(heap.size()){
            PII now = heap.top(); heap.pop();
            int nod = now.second;
            if(vis[nod]) continue;
            vis[nod] = 1;
            for(auto j : vec[nod]){
                int To = j.first, distmp = j.second;
                if(dist[To] > now.first + distmp){
                    path[To] = nod;  //记录路径
                    dist[To] = now.first + distmp;
                    heap.push({dist[To], To});
                }
            }
        }
        return;
    }
    void MIN(int x, int y, ll z){
        if(minn > startdist[x] + enddist[y] + z){
            change = 1;
            minn = startdist[x] + enddist[y] + z;
            transferstart = x, transferend = y, transferval = z;
        }
    }
    void printpath(int s, int d, ll path[], int Reverse){  //打印路径
        stack<int> st, tmp, anst;
        while(st.size()) st.pop();
        while(tmp.size()) tmp.pop();
        while(anst.size()) anst.pop();
        st.push(d);
        while(path[d] != -1){
            st.push(path[d]);
            d = path[d];
        }
        if(Reverse){  //反转
            while(st.size()){
                tmp.push(st.top());
                st.pop();
            }
            st = tmp;
        }
        while(st.size() > 1){  //末尾不能有空格
            printf("%d ", st.top());
            st.pop();
        }
        printf("%d", st.top()); st.pop();
    }
    int main(){
        while(cin >> n >> s >> d){
            init();
            cin >> m;
            rep(i, 1, m){
                cin >> x >> y >> z;
                vec[x].push_back({y, z}), vec[y].push_back({x, z});
            }
            dijkstra(s, startdist, startpath);
            dijkstra(d, enddist, endpath);
            cin >> k;
            minn = INF;
            rep(i, 1, k){
                cin >> x >> y >> z;
                MIN(x, y, z), MIN(y, x, z);
            }
            if(cas++) printf("\n");  //各组数据之间有空行
            if(startdist[d] <= minn && startdist[d] != INF){  //无需商业线
                printpath(s, d, startpath, 0);
                printf("\nTicket Not Used\n%d\n", startdist[d]);  //直接输出起点到终点最短路
            }
            else{
                printpath(s, transferstart, startpath, 0);
                printf(" ");
                printpath(d, transferend, endpath, 1);  //逆序
                printf("\n%d\n%d\n", transferstart, minn);
            }
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值