3.1.1 单源最短路的建图方式

总览

在这里插入图片描述

单源最短路的一些算法

{ 边 权 非 负 { 朴 素 D i j k s t r a 堆 优 化 D i j k s t r a 有 负 权 边 { B e l l m a n − F o r d ( 用 的 情 况 少 ) s p f a ( 99 % 情 况 用 这 种 算 法 ) \left\{ \begin{matrix}边权非负\left\{\begin{matrix} 朴素Dijkstra \\ 堆优化Dijkstra \end{matrix}\right.\\ 有负权边\left\{\begin{matrix} Bellman-Ford (用的情况少)\\ spfa (99\%情况用这种算法) \end{matrix}\right.\\ \end{matrix} \right. {DijkstraDijkstra{BellmanFordspfa99%
图论问题,最重要的在于问题的转化与抽象
在这里插入图片描述

AcWing 1129. 热浪

分析

无向图,求起点到终点的最短路径(裸题)
最简单的单源最短路模型

朴素版dijkstra 时间复杂度 O ( n 2 ) O(n^2) O(n2) 250 0 2 2500^2 25002可以过
堆优化版dijkstra 时间复杂度 O ( m l o g n ) O(mlogn) O(mlogn), 6200 ∗ l o g 2500 6200 * log {2500} 6200log2500 可以过
spfa 平均时间复杂度 O ( m ) O(m) O(m), 也可以过
随便选择一个。
其中,
spfa可以采用循环队列, 来节省队列长度

while (hh != tt){
	if (hh == N) hh = 0;
	...
	if (tt == N) tt = 0;
}

代码

#include <iostream>
#include <cstring>
using namespace std;
const int N = 2510, M = 6210 * 2;
int n, m, S, E;
int h[N], e[M], w[M], ne[M], idx;
int dist[N], q[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 ++;
}

int spfa(){
    memset(dist, 0x3f, sizeof dist);
    dist[S] = 0;
    int hh = 0, tt = 1; // tt表示当前位置, 还没放数字, 因此后面是tt ++, 而不是 ++ tt
    q[0] = S, st[S] = true;
    
    while (hh != tt){
        int t = q[hh ++];
        if (hh == N) hh = 0;
    
        st[t] = false;
        
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                if (!st[j]){
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = true;
                }
            }
        }
    }
    return dist[E];
}

int main(){
    cin >> n >> m >> S >> E;
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < m; i ++ ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    
    int t = spfa();
    
    cout << t << endl;
    
    return 0;
}

AcWing 1128. 信使

分析

题目问整个图中, 所有点被广播到需要多少时间,转化一下,就是求最短距离中最长的距离.
这有点意思啊…

dijkstra代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 110, M = 210 * 2, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;

int n, m;
int dist[N];
bool st[N];
int g[N][N];

int dijkstra(){
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});
    
    while (heap.size()){
        auto t = heap.top(); heap.pop();
        int ver = t.second, distance = t.first;
        if (st[ver]) continue;
        
        st[ver] = true;
        
        for (int j = 1; j <= n; j ++ ){
            if (dist[j] > dist[ver] + g[ver][j]){
                dist[j] = dist[ver] + g[ver][j];
                heap.push({dist[j], j});
            }
                
        }
    }
}
int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )  
        for (int j = 1; j <= n; j ++ )  
            if (i != j) g[i][j] = g[j][i] = INF;
            else g[i][j] = g[j][i] = 0;
            
    for (int i = 0; i < m; i ++ ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    int t = dijkstra();
    
    int res = 0;
    for (int i = 2; i <= n; i ++ ) res = max(res, dist[i]);
    
    if (res == 0x3f3f3f3f) cout << -1 << endl;
    else cout << res << endl;
    
    return 0;
        
}

floyd 代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 110, M = 210 * 2, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;

int n, m;
int dist[N][N];
bool st[N];
int g[N][N];

void 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]);
}

int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )  
        for (int j = 1; j <= n; j ++ )  
            if (i != j) dist[i][j] = dist[j][i] = INF;
            else dist[i][j] = dist[j][i] = 0;
            
    for (int i = 0; i < m; i ++ ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        dist[a][b] = dist[b][a] = min(dist[a][b], c);
    }
    
    floyd();
    
    int res = 0;
    for (int i = 2; i <= n; i ++ ) res = max(res, dist[1][i]);
    
    if (res == 0x3f3f3f3f) cout << -1 << endl;
    else cout << res << endl;
    
    return 0;
        
}

AcWing 1127. 香甜的黄油

分析

就是求图中一个点 到 给出的所有奶牛位置的最短距离之和的最小值
在求最短路的时候, 外层还要套一层n次起点.
在这里插入图片描述

floyed(TLE)

#include <iostream>
#include <cstring>
using namespace std;
const int N = 810, M = 1460 * 2, INF = 0x3f3f3f3f;
int d[N][N];
int n, P, m;
int a[N];

void floyd(){
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = d[j][i] = min(d[i][j], d[i][k] + d[k][j]);
}

int main(){
    cin >> P >> n >> m;
    for (int i = 0; i < P; i ++ ){
        cin >> a[i];
    }
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i != j) d[i][j] = d[j][i] = INF;
            else d[i][i] = 0;
            
    for (int i = 1; i <= m; i ++ ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        d[a][b] = d[b][a] = min(d[a][b], c);
    }
    
    // cout << d[2][4] << d[3][4] << endl;
            
    floyd();
    
    int res = INF;
    for (int i = 1; i <= n; i ++ ) {
        int t = 0;
        for (int j = 0; j < P; j ++ )
            t += d[a[j]][i];
        res = min(t, res);
    }
            
    cout << res << endl;
    
    return 0;
    
}

spfa

#include <iostream>
#include <cstring>
using namespace std;
const int N = 810, M = 1460 * 2, INF = 0x3f3f3f3f;
int h[N], e[M], w[M], ne[M], idx;
bool st[N];
int q[N];
int n, p, m;
int id[N];
int dist[N];

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

int spfa(int start){
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;
    int hh = 0, tt = 1;
    q[0] = start, st[start] = true;
    
    while (hh != tt){
        int t = q[hh ++];
        if (hh == N) hh = 0;
        st[t] = false;
        
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                if (!st[j]){
                    q[tt ++] = j;
                    if (tt == N) tt =  0;
                    st[j] = true; 
                }
            }
        }
    }
    
    int res = 0; // 计算所有奶牛到起点的距离
    for (int i = 0; i < n; i ++ ){
        int j = id[i];
        int t = dist[j];
        if (t == INF) return INF;
        else res += t;
    }
    return res;
}
int main(){
    cin >> n >> p >> m;
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < n; i ++ ) cin >> id[i];
    
    for (int i = 0; i < m; i ++ ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    
    int res = INF;
    for (int i = 1; i <= p; i ++ ) res = min(res, spfa(i)); // 计算所有起点开始到奶牛的最小距离
    
    cout << res << endl;
    
    return 0;
}

堆优化dijkstra

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 810, M = 1460 * 2, INF = 0x3f3f3f3f;
int h[N], e[M], w[M], ne[M], idx;
bool st[N];
int q[N];
int n, p, m;
int id[N];
int dist[N];
typedef pair<int, int> PII;
void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

int dijkstra(int start){
    memset(st, 0, sizeof st); // st数组每次需要重制. 别忘了
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, start});
    
    while (heap.size()){
        auto t = heap.top(); heap.pop();
        int ver = t.second, distance = t.first;
        if (st[ver]) continue;
        
        st[ver] = true;
        for (int i = h[ver]; ~i; i = ne[i]){
            int j = e[i];
            if (dist[j] > dist[ver] + w[i]){
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }
    int res = 0;
    for (int i = 0; i < n; i ++ ){
        int j = id[i];
        if (dist[j] == INF) return INF;
        res += dist[j];
    }
    return res;
}
int main(){
    cin >> n >> p >> m;
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < n; i ++ ) cin >> id[i];
    
    for (int i = 0; i < m; i ++ ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    
    int res = INF;
    for (int i = 1; i <= p; i ++ ) res = min(res, dijkstra(i)); // 计算所有起点开始到奶牛的最小距离
    
    cout << res << endl;
    
    return 0;
}

AcWing 1126. 最小花费

分析

在这里插入图片描述

A 最少需要多少钱使得转账后 B 收到 100 元
100 = d(A) * W1 * W2 * … Wn

要让 A的值最小 则(W1 * W2 * … Wn)最大

证明最短路模型对于 乘法也成立

100 = d(A) * W1 * W2 * … Wn
对W1 * W2 * … Wn 取log, 那么每条边权重会变成 l o g ( w i ) log(w_i) log(wi),题目转化为求A到B的和最大的路径(因为 W i W_i Wi乘法变成加法了, 可以用最短路模型了) , 对每条边* (-1), 使得权重变成正的, 就会转化成权重为 − l o g ( w i ) > 0 -log(w_i) > 0 log(wi)>0的最短路问题.
0 < w i ≤ 1 0 < w_i \leq 1 0<wi1, l o g ( w i ) < 0 log(w_i) < 0 log(wi)<0.

注意

w i ≤ 1 w_i \leq 1 wi1, 使得取完log * (-1), 权重都为正的, 所以才可以dijkstra. 如果没有这个条件, 只能spfa
在这里插入图片描述

spfa代码

#include <iostream>
#include <cstring>
using namespace std;
const int N = 2010, M = 100010 * 2;

int h[N], e[M], w[M], ne[M], idx;

int n, m;
int S, T;
double dist[N];
int q[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 ++;
}

void spfa(){
    dist[T] = 1;
    
    int hh = 0, tt = 1;
    q[0] = T, st[T] = true;
    
    while (hh != tt){
        int t = q[hh ++];
        if (hh == N) hh = 0;
        st[t] = false;
        
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (dist[j] < (double)dist[t] * (double)(100 - w[i]) / 100.0){
                dist[j] = (double)dist[t] * (double)(100 - w[i]) / 100.0;
                if (!st[j]){
                    q[tt ++] = j;
                    if (tt == N) tt = 0;
                    st[j] = true;
                }
            }
        }
    }
}

int main(){
    cin >> n >> m;
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < m; i ++ ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    cin >> S >> T;
    spfa();
    
    printf("%.8lf\n", 100.0 / dist[S]);
    
    return 0;
}

朴素dijkstra代码

#include <iostream>
#include <cstring>
using namespace std;
const int N = 2010, M = 100010 * 2;

int h[N], e[M], w[M], ne[M], idx;

int n, m;
int S, T;
double dist[N];
bool st[N];
double g[N][N];

void dijkstra(){
    // 因为求最大, 所以dist = 0, 表示此路不通
    dist[S] = 1;
    for (int i = 0; i < n; i ++ ){
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] < dist[j])) t = j; // 找出s中距离原点最大的
            

        st[t] = true;
        for (int j = 1; j <= n; j ++)
            dist[j] = max(dist[j], dist[t] * g[t][j]);
    }
}

int main(){
    cin >> n >> m;
    // g = 0表示路不通
    for (int i = 0; i < m; i ++ ){
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = max(g[a][b], (100 - c) / 100.0);
    }
    cin >> S >> T;
    
    dijkstra();
    
    
    printf("%.8lf\n", 100.0 / dist[T]);
    
    return 0;
}

总结

对于乘法的最短路问题.
如果权重 > 1, 那么可以dijkstra
如果权重 >= 0, 那么只能spfa
在这里插入图片描述

AcWing 920. 最优乘车

分析

在这里插入图片描述
可以先求坐车的最小次数, 然后 - 1, 就是换车的最小次数
然后再每段车的起点,往后面的终点连一条边权重为1的边, 然后求1-n的最短路径, 再-1,就是换车次数了
特判下 1 == n, 会减成-1, 输出0.
边权为1的话,用bfs就可以了
在这里插入图片描述

代码

#include <iostream>
#include <cstring>
#include <sstream>
using namespace std;
const int N = 510;
int dist[N];
int g[N][N];
int n, m;
int q[N];
int stop[N];

void bfs(){
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    int hh = 0, tt = 0;
    q[0] = 1;
    
    while (hh <= tt){
        int t = q[hh ++];
        // bfs不用判重, 每次出队就是最小值
        for (int i = 1; i <= n; i ++ )
            if (g[t][i] && dist[i] > dist[t] + 1){
                dist[i] = dist[t] + 1;
                q[++ tt] = i;
            }

    }
}

int main(){
    cin >> m >> n;
    string line;
    getline(cin, line);
    while (m -- ){
        getline(cin, line);
        stringstream ssin(line);
        int cnt = 0, p;
        while (ssin >> p) stop[cnt ++] = p;
        for (int j = 0; j < cnt; j ++ )
            for (int k = j + 1; k < cnt; k ++ )
                g[stop[j]][stop[k]] = true;
    }
    
            
    bfs();
    
    if (dist[n] == 0x3f3f3f3f) puts("NO");
    else cout << max(0, dist[n] - 1) << endl;
    
    return 0;
}

AcWing 903. 昂贵的聘礼

分析

读题, 发现一个物品, 有多条路径, 其中一种方式是某个点直接可以和这个点相连, 在其他物品中也发现都存在这个点, 可以将这个点建立为虚拟源点
然后对于等级限制, 暴力枚举从最低范围内的等级为起点, 最高范围为酋长, 求最短路
在这里插入图片描述

代码

#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int w[N][N], dist[N];
bool st[N];
int n, m;
int level[N];


int dijkstra(int down, int up){
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    dist[0] = 0;
    
    for (int i = 1; i <= n + 1; i ++ ){
        int t = -1;
        for (int j = 0; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
            
        st[t] = true;
        for (int j = 1; j <= n; j ++ )
            if (level[j] >= down && level[j] <= up)
                dist[j] = min(dist[j], dist[t] + w[t][j]);
    }
    return dist[1];
}

int main(){
    cin >> m >> n;
    memset(w, 0x3f, sizeof w);
    for (int i = 1; i <= n; i ++ ) w[i][i] = 0;
    
    for (int i = 1; i <= n; i ++ ){
        int price, cnt;
        cin >> price >> level[i] >> cnt;
        w[0][i] = min(w[0][i], price);
        while (cnt --){
            int id, cost;
            cin >> id >> cost;
            w[id][i] = min(w[id][i], cost);
        }
    }
    
    int res = INF;
    for (int i = level[1] - m; i <= level[1]; i ++ )  res = min(res, dijkstra(i, i + m));
    
    cout << res << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值