曾经沧海难为水,除却巫山不是云。|

Joey-Wang

园龄:4年3个月粉丝:17关注:0

10.4 最短路径

10.4 最短路径

http://codeup.hustoj.com/contest.php?cid=100000621

C 最短路径

image-20200830163030532

题目解析

❗️这道题有个大坑,一开始用Dijkstra做WA了,每条路的长度(权重)是2^K,K∈[0, 500],所以肯定不能直接存的,会爆int,所以这里用了快速幂求解。但问题就是存的长度是MOD 100000 之后的值,就根本没办法简单比较大小。

看了网上的题解,不用Dijsktra,关键在于只要前面输入的点之间是连通的,这些路径就是最短路径
因为2^0 + 2^1 + …… + 2^(k-1) = 2^k-1 < 2^k,证明前面若连通,即使是所有路径长度相加都比下一条路径短

所以这道题用并查集做,只需根据尽可能早出现的两点(越早输入则两点间距离越短),生成的连通集合间各点距离越短【据说是最小生成树题】:
1️⃣ 使用dis[][]数组记录两点之间的最短距离(⚠️ 根据题目,dis[i][i]=0别忘了)
2️⃣ 按题目要求读入两点a,b,若a,b是不连通的(即父结点不是一个),就要计算之间距离d,将它们合并为一个集合,则需通过这条边,更新所有a、b集合内的点之间的距离;若a,b是连通的(即父结点是一个)就不用管这条路了,因为此集合中各点之间的距离肯定比算上这条边要短。
🔴 其实这样算出了所有点到其他点的最短距离【全源最短路】,但最后只需要起点0到其他点的最短路,故最后遍历输出d[0][i]

代码(Dijkstra——WA)

#include <cstdio>
#include <algorithm>

using namespace std;
#define INF 0x3fffffff
#define maxn 105
typedef long long LL;
int n, G[maxn][maxn];
bool vis[maxn] = {false};
int d[maxn];

LL binaryPow(LL a, LL b, LL m) {
    if (b == 0) return 1;
    if (b % 2 != 0) return a * binaryPow(a, b - 1, m) % m;
    else {
        int temp = binaryPow(a, b / 2, m);
        return temp * 2 % m;
    }
}

void Dijkstra(int s) {
    fill(d, d + maxn, INF);
    d[s] = 0;
    for (int i = 0; i < n; i++) {  //循环n次
        int u = -1, MIN = INF;
        for (int j = 0; j < n; j++) {  //找到一个没被标记且与起点s最小距离最小的点
            if (!vis[j] && d[j] < MIN) {
                u = j;
                MIN = d[j];
            }
        }
        if (u == -1) return;
        vis[u] = true;
        for (int v = 0; v < n; v++) {
            if (!vis[v] &&G[u][v]!=INF && d[u] + G[u][v] < d[v]) { //V-S中的v且u可达
                d[v] = d[u] + G[u][v];
            }
        }
    }
}

int main() {
    int m, a, b, w;
    fill(G[0], G[0] + maxn * maxn, INF);

    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i++) {
        scanf("%d%d", &a, &b);
        w = binaryPow(2, i, 100000);
        G[a][b] = w;
        G[b][a] = w;
    }
    for (int i = 0; i < n; i++) {
        G[i][i] = 0;
    }
    Dijkstra(0);
    for (int i = 1; i < n; i++) {
        if (d[i] == INF) printf("-1\n");
        else printf("%d\n", d[i]);
    }
    return 0;
}

代码(并查集——AC)

#include <cstdio>
#include <algorithm>

using namespace std;
#define INF -1
#define maxn 105
typedef long long LL;
int n, dis[maxn][maxn];
int father[maxn];

LL binaryPow(LL a, LL b, LL m) {
    if (b == 0) return 1;
    if (b % 2 ==1) return a * binaryPow(a, b - 1, m) % m;
    else {
        LL temp = binaryPow(a, b / 2, m);  ///!!! 是LL
        return temp * temp % m;
    }
}

int findFather(int x) {
    if (x == father[x]) return x;
    else {
        int v = findFather(father[x]);
        father[x] = v;
        return v;
    }
}

int main() {
    int m, a, b, d;
    while(scanf("%d%d", &n, &m)!=EOF){
        fill(dis[0], dis[0] + maxn * maxn, INF);
        for (int i = 0; i < n; i++) {
            dis[i][i] = 0;
            father[i] = i;
        }
        for (int i = 0; i < m; i++) {
            scanf("%d%d", &a, &b);
            int x = findFather(a);
            int y = findFather(b);
            if (x != y) { //若这条路径的两个顶点在同一集合中,则不需要更新,因为距离是越来越大的 1+2^1+······+2^(k-1)=2^k-1 < 2^k
                //更新距离,合并二者
                d = binaryPow(2, i, 100000);
                // 通过新联系a至b, 更新两个集合中各点之间的最短距离
                for (int u = 0; u < n; u++) {
                    if (findFather(u) == x) { //找到以 x 为根节点的集合上所有点
                        for (int v = 0; v < n; v++) {
                            if (findFather(v) == y) { //找到以 y 为根节点的集合上所有点
                                dis[u][v] = dis[v][u] = (dis[u][a] + d + dis[b][v]) % 100000;
                            }
                        }
                    }
                }
                //合并这两个集合
                father[y] = x;
            }
        }
        for(int i=1; i<n; i++) printf("%d\n", dis[0][i]);
    }
    return 0;
}

D 最短路径

image-20200830165134053

题目解析

这里都采用了Dijsktra+DFS的做法,⚠️ 需注意的是要输出字典序最小的那条,因为使用vector记录tempPath与path,所以可以直接用比较符进行比较,但是二者中点都是倒序的,最后也是倒序输出,所以若要字典序最小,则tempPath > path。

一开始用邻接矩阵做,一直错误50,不知道为啥,网上有博主说这道题会出现重复的边,所以要用邻接表做。用邻接表确实AC了,但是最后在邻接矩阵代码中加入判断 if (w < G[a][b]) G[a][b] = G[b][a] = w; 让可能出现的重复边权重一直保持最小的,还是WA,不知道为啥了🙃

这里用邻接表做,想特别提醒下❗️一定要特别注意题目中各点是从0开始,还是从1开始,因为vector中push_back后遍历都是从0开始,一开始在👇这里WA了半天没看出来,当初写得

//正确写法
for (int j = 0; j < Adj[u].size(); j++) {
    int v=Adj[u][j].v; ///注意顶点v是从1开始,但是对应的Adj中的点是j=0开始
    if (!vis[v]) {
        if (d[u] + Adj[u][j].dis < d[v]) {
            d[v] = d[u] + Adj[u][j].dis;
            pre[v].clear();
            pre[v].push_back(u);
        } else if (d[u] + Adj[u][j].dis == d[v]) {
            pre[v].push_back(u);
        }
    }
}
//错误写法
for (int v = 0; v < Adj[u].size(); v++){ 
    if (!vis[Adj[u][v].v]){
        if (d[u] + Adj[u][v].dis < d[v]){ //这里d[v]错了,应该是d[Adj[u][v].v]
            ……
        }
        ……
    }
}

代码(邻接矩阵——WA)

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 1005
#define INF 0x3fffffff
int n, m, st, ed;
int G[maxn][maxn];
bool vis[maxn] = {false};
int d[maxn];
vector<int> pre[maxn];//存放每个结点的所有前驱结点
vector<char> tempPath;
vector<char> path;

void Dijkstra(int s) {
    fill(d, d + maxn, INF);
    d[s] = 0;
    for (int i = 1; i <= n; i++) { //循环n次
        int u = -1, MIN = INF;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && d[j] < MIN) {
                u = j;
                MIN = d[j];
            }
        }
        if (u == -1) return;
        vis[u] = true;
        for (int v = 1; v <= n; v++) {
            if (!vis[v] && G[u][v] != INF) {
                if (d[u] + G[u][v] < d[v]) {
                    d[v] = d[u] + G[u][v];
                    pre[v].clear();
                    pre[v].push_back(u);
                } else if (d[u] + G[u][v] == d[v]) {
                    pre[v].push_back(u);
                }
            }
        }
    }
}

// 起点s,从后往前遍历到顶点u
void DFS(int s, int u) {
    if (u == s) {
        tempPath.push_back(s);
        if (tempPath > path || path.empty()) path = tempPath; // 要得到字典序最小的,则要tempPath > path,因为path中是倒序的
        tempPath.pop_back();
        return;
    }
    tempPath.push_back(u);
    for (int i = 0; i < pre[u].size(); i++) {
        DFS(s, pre[u][i]);
    }
    tempPath.pop_back();
}

int main() {
    int a, b, w;
    while (scanf("%d%d%d%d", &n, &m, &st, &ed) != EOF) {
        fill(G[0], G[0] + maxn * maxn, INF);
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= n; i++) pre[i].clear();
        tempPath.clear();
        path.clear();
        for (int i = 1; i <= m; i++) {
            scanf("%d%d%d", &a, &b, &w);
            if (w < G[a][b]) G[a][b] = G[b][a] = w;
        }
        Dijkstra(st);
        if (d[ed] == INF) printf("can't arrive\n");
        else {
            DFS(st, ed);
            printf("%d\n", d[ed]);
            for (int i = path.size() - 1; i >= 0; i--) { //倒着输出
                printf("%d ", path[i]);
            }
            printf("\n");
        }
    }
    return 0;
}

代码(邻接表——AC)

#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>
#define maxn 10005
#define INF 0x3fffffff
using namespace std;
int n, m, st, ed;

struct node {
    int v;
    int dis;

    node(int _v, int _dis) : v(_v), dis(_dis) {}
};

vector<node> Adj[maxn];
bool vis[maxn];
int d[maxn];
vector<int> pre[maxn];
vector<int> tempPath;
vector<int> path;

void Dijkstra(int s) {
    fill(d, d + maxn, INF);
    d[s] = 0;
    for (int i = 1; i <= n; i++) {
        int u = -1, MIN = INF;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && d[j] < MIN) {
                u = j;
                MIN = d[j];
            }
        }
        if (u == -1) return;
        vis[u] = true;
        for (int j = 0; j < Adj[u].size(); j++) {
            int v=Adj[u][j].v; ///注意顶点v是从1开始,但是对应的Adj中的点是j=0开始
            if (!vis[v]) {
                if (d[u] + Adj[u][j].dis < d[v]) {
                    d[v] = d[u] + Adj[u][j].dis;
                    pre[v].clear();
                    pre[v].push_back(u);
                } else if (d[u] + Adj[u][j].dis == d[v]) {
                    pre[v].push_back(u);
                }
            }
        }
    }
}

void DFS(int s, int u) {
    if (s == u) {
        tempPath.push_back(s);
        if (tempPath > path || path.empty()) path = tempPath;
        tempPath.pop_back();
        return;
    }
    tempPath.push_back(u);
    for (int i = 0; i < pre[u].size(); i++) {
        DFS(s, pre[u][i]);
    }
    tempPath.pop_back();
}

int main() {
    int a, b, w;
    while (scanf("%d%d%d%d", &n, &m, &st, &ed) != EOF) {
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= n; i++) {
            Adj[i].clear();
            pre[i].clear();
        }
        tempPath.clear();
        path.clear();
        for (int i = 0; i < m; i++) {
            scanf("%d%d%d", &a, &b, &w);
            Adj[a].push_back(node(b, w));
            Adj[b].push_back(node(a, w));
        }
        Dijkstra(st);
        if (d[ed] == INF) printf("can't arrive\n");
        else {
            printf("%d\n", d[ed]);
            DFS(st, ed);
            for (int i = path.size() - 1; i >= 0; i--) printf("%d ", path[i]);
            printf("\n");
        }
    }
    return 0;
}

E 最短路径问题

image-20200830165837960

题目解析

这道题还是很常规的,用Dijkstra做出来没啥问题。看了提示后尝试直接用DFS做,也AC了。

代码(Dijkstra)

#include <cstdio>
#include <vector>
#include <algorithm>

#define maxn 1005
#define INF 0x3fffffff
using namespace std;
int n, dis[maxn][maxn];
int cost[maxn][maxn];
bool vis[maxn];
int d[maxn], c[maxn];

void Dijkstra(int s) {
    fill(d, d + maxn, INF);
    fill(c, c + maxn, INF);
    d[s] = 0;
    c[s] = 0;
    for (int i = 1; i <= n; i++) {
        int u = -1, MIN = INF;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && d[j] < MIN) {
                u = j;
                MIN = d[j];
            }
        }

        if (u == -1) return;
        vis[u] = true;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && dis[u][j] != INF) {
                if (d[u] + dis[u][j] < d[j]) {
                    d[j] = d[u] + dis[u][j];
                    c[j] = c[u] + cost[u][j];
                } else if (d[u] + dis[u][j] == d[j] && c[u] + cost[u][j] < c[j]) {
                    c[j] = c[u] + cost[u][j];
                }
            }
        }
    }
}

int main() {
    int m, a, b, dd, p, s, t;
    while (scanf("%d%d", &n, &m) && n && m) {
        fill(dis[0], dis[0] + maxn * maxn, INF);
        fill(cost[0], cost[0] + maxn * maxn, INF);
        fill(vis, vis + maxn, 0);
        for (int i = 0; i < m; i++) {
            scanf("%d%d%d%d", &a, &b, &dd, &p);
            dis[a][b] = dis[b][a] = dd;
            cost[a][b] = cost[b][a] = p;
        }
        scanf("%d%d", &s, &t);
        Dijkstra(s);
        printf("%d %d\n", d[t], c[t]);
    }
    return 0;
}

代码(DFS)

#include <cstdio>
#include <algorithm>

#define maxn 1005
#define INF 0x3fffffff
using namespace std;
int dis[maxn][maxn];
int cost[maxn][maxn];
int vis[maxn];
int n, s, t;
int ansD, ansP;

//当前遍历到点u
void DFS(int u, int totalD, int totalP) {
    if (totalD > ansD || (totalD == ansD && totalP > ansP)) return; //剪枝
    if (u == t) {
        ansD = totalD;
        ansP = totalP;
        return;
    }
    for (int i = 1; i <= n; i++) {
        if (!vis[i] && dis[u][i] != INF) {
            vis[i] = true;
            DFS(i, totalD + dis[u][i], totalP + cost[u][i]);
            vis[i] = false;
        }
    }
}

int main() {
    int m, a, b, d, p;
    while (scanf("%d%d", &n, &m) && n && m) {
        fill(dis[0], dis[0] + maxn * maxn, INF);
        fill(cost[0], cost[0] + maxn * maxn, INF);
        fill(vis, vis + maxn, false);
        ansD = INF, ansP = INF;
        for (int i = 0; i < m; i++) {
            scanf("%d%d%d%d", &a, &b, &d, &p);
            dis[a][b] = dis[b][a] = d;
            cost[a][b] = cost[b][a] = p;
        }
        scanf("%d%d", &s, &t);
        vis[s] = true;
        DFS(s, 0, 0);
        printf("%d %d\n", ansD, ansP);
    }
    return 0;
}

本文作者:Joey-Wang

本文链接:https://www.cnblogs.com/joey-wang/p/14541191.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Joey-Wang  阅读(61)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开