单源最短路

单源最短路算法

Dijkstra算法

只能求解非负边权的图中最短路

朴素Dijkstra

常用于稠密图,时间复杂度: \(O(n*m)\)

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

const int INF = 0x3f;
const int N = 510;
int n,m;
int g[N][N];    //邻接矩阵存图
int dist[N];
int st[N];

int dijkstra()
{
    
    memset(dist, INF, sizeof dist);
    dist[1] = 0;
    
    for (int i = 1; i < n; i ++ ){
        
        int t = -1;
        for (int j = 1; j <= n; j ++ )          //寻找下一个没有确定最短路的结点
            if (!st[j] && (t == -1 || dist[t] > dist[j]))    //所有未确定的点中寻找一个距离最短的点
                t = j;
            
        st[t] = 1;      //标记为访问过
        
        for (int j = 1; j <= n; j ++ )      //用t来更新下一个点的最短距离
            dist[j] = min(dist[j], dist[t] + g[t][j]);
        
    }
    
    if (dist[n] == 0x3f3f3f3f)
        return -1;
    else
        return dist[n];
    
}



int main()
{
    
    cin >> n >> m;
    
    memset(g, INF, sizeof g);   //初始化边权为INF
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        
        g[a][b] = min(g[a][b], v);  //对于重边,取最小值
        
    }
    
    cout << dijkstra() << endl;
    

    return 0;
    
}

堆优化Dijkstra

常用于稀疏图, 时间复杂度: \(O(mlogn)\)

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

typedef pair<int,int> PII;
const int INF = 0x3f3f;
const int N = 1e5 + 10;
const int M = 2 * N;
int n, m;
int h[M];
int w[M];   //存储边权
int e[M];
int ne[M];
int idx;
int st[N];
int dist[N];

void add(int a, int b, int v)   //由a到b的一条权值为v的边
{
    e[idx] = b;
    w[idx] = v;
    ne[idx] = h[a];
    h[a] = idx ++;
}

int dijkstra()
{
    memset(dist, INF, sizeof dist);
    dist[1] = 0;
    
    priority_queue<PII, vector<PII>, greater<PII> > heap;
    heap.push({0,1});           //这里用first存储距离,这样heap对距离进行优先排序
    
    while (heap.size()){
        
        auto it = heap.top();
        heap.pop();
        
        int u = it.second;      //当前结点
        int v = it.first;       //获得当前结点到起始点的距离
        
        if (st[u])              //该节点已经求出最短距离
            continue;
        
        st[u] = 1;
        
        for (int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            if (dist[j] > v + w[i]){        //该节点的距离可以被更新
                dist[j] = v + w[i];
                heap.push({dist[j], j});    //结点入队
            }
            
        }
    }
    if (dist[n] == 0x3f3f3f3f)
        return -1;
    else
        return dist[n];
}



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

}

SPFA算法

可以处理负权边,还可以判断是否存在负环(经常被卡)

优化的Bellman-Ford算法, 时间复杂度:平均 \(O(m)\), 最坏 \(O(n*m)\)

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int N = 1e5 + 10;

int n, m;int h[N];int e[N];int w[N];int ne[N];int idx;int dist[N];int st[N];

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


int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    queue<int> q;
    q.push(1);
    st[1] = 1;      
    
    while (q.size()){
        int u = q.front();
        q.pop();
        st[u] = 0;              //结点出队
        //用当前结点去更新其它点
        for (int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            if (dist[j] > dist[u] + w[i]){      //该点需要被更新了
                dist[j] = dist[u] + w[i];
                
                if (!st[j]){        //当前点不在队列中
                    q.push(j);
                    st[j] = 1;
                }
            }
        }
    }
    
    if (dist[n]  > 0x3f3f3f3f / 2)
        return -1;
    else    
        return dist[n];
}


int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        add(a, b, v);
    }
    
    int t = spfa();
    
    if (t == -1)
        puts("impossible");
    else
        cout << t << endl;
    
    return 0;
    
}

单源最短路的简单建图

1129. 热浪

算法思路:

最短路模板题,Dijkstra或SPFA均可过(注意是双向边)

堆优化Dijkstras算法:
#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

typedef pair<int,int> PII;
const int INF = 0x3f;
const int N = 2510, M = 22000;
int n, m;
int s, z;
int h[M];
int ne[M];
int e[M];
int w[M];
int dist[M];
int idx;
int st[M];

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

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[s] = 0;
    
    priority_queue<PII,vector<PII>,greater<PII>>heap;
    heap.push({0,s});
    
    while (heap.size()){
        
        auto it = heap.top();
        heap.pop();
        
        int u = it.second;
        int distance = it.first;
        
        if(st[u])
            continue;
        st[u] = 1;
        
        for (int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            if (dist[j] > distance + w[i]){
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }
    
    return dist[z];
    
}


int main()
{
    cin >> n >> m >> s >> z;
    memset (h, -1, sizeof h);
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        add(a, b, v);
        add(b, a, v);
    }
    
    cout << dijkstra() << endl;
    
    return 0;
    
}

1128. 信使

算法思路:

同样是最短路的模板题,但是求出起点到各个点的最短路后,需要求出其中的最大值,即为所需要的最少时间

#include <iostream>
#include <cstring>

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 500;
int g[N][N];
int dist[N];
int st[N];
int n, m;

int dijkstra()
{
    int res = 0;
    memset(dist, INF, sizeof dist);
    dist[1] = 0;
    
    for (int i = 1; i <= n; i ++ ){
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        st[t] = 1;
        
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    
    for (int i = 1; i <= n; i ++ )
        if(dist[i] == INF)
            return -1;
        else
            res = max(dist[i],res);
    return res;
}

int main()
{
    cin >> n >> m;
    memset(g, INF, sizeof g);
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        g[a][b] = g[b][a] = min(g[a][b], v);
    }

    
    cout << dijkstra() << endl;
    
    return 0;
    
}

1127. 香甜的黄油

算法思路:

需要找出一个牧场,满足到其它牧场距离之和最短,所以枚举每个牧场作为起点,计算所有牛到该牧场的最短距离之和

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int INF = 0x3f3f3f3f;
const int N = 5000;

int n,m,cows;
int cow[N];
int h[N];
int e[N];
int ne[N];
int idx;
int w[N];

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

int spfa(int x)
{
    int dist[N];
    int st[N];
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    dist[x] = 0;
    
    queue<int> q;
    q.push(x);
    st[x] = 1;
    
    while (q.size()){
        int u = q.front();
        q.pop();
        st[u] = 0;
        
        for (int i = h[u]; i != -1; 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] = 1;
                    q.push(j);
                }
            }
        }
    }
    
    int ans = 0;
    
    for (int i = 1; i <= cows; i ++ ){
        int j = cow[i];
        if (dist[j] == INF)
            return INF;
        else
            ans += dist[j];
    }
    return ans;
    
}

int main()
{
    cin >> cows >> n >> m;
    memset(h, -1, sizeof h);
    memset(cow, 0 ,sizeof cow);
    int x;
    //cout << INF << endl;
    for (int i = 1; i <= cows; i ++ ){
        cin >> cow[i];
    }
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        add(a, b, v);
        add(b, a, v);
    }
    int ans = INF;
    
    for (int i = 1; i <= n; i ++ )
        ans = min(ans, spfa(i));
    
    
    cout << ans << endl;
    
    return 0;
    
}

1126. 最小花费

算法思路:

从A点出发,每经过一条路,每次乘路径上对应的权值\(w\) (\(0<w <=1\)), 到达B后剩余100元,求最小损失。只需求出路径权重之积最大即可。

题目拓展:

一般最短路问题需要加上边权重,当需要每次乘边权重时,可以进行分类(证明使用\(log\)的乘积性质):

  1. \(w>1\) : 转化为经过路径之积最小问题, Dijkstra与SPFA均可(边权为正)

  2. \(0<w<=1\) : 转化为经过路径之积最大问题,Dijkstra与SPFA均可(边权均为负,转化为正)

  3. \(w>0\) : 只能使用SPFA,转化为求路径之积最小问题(边权有负有正)

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 2010;
int n,m;
double g[N][N];
double dist[N];
int st[N];
int A,B;

double dijkstra()
{
    memset(dist, 0, sizeof dist);
    dist[A] = 1;
    
    for (int i = 1; i <= n; i ++ ){
        int t = -1;
        
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[j] > dist[t]))
                t = j;
        
        st[t] = 1;
        
        for (int j = 1; j <= n; j ++ )
            dist[j] = max(dist[j], dist[t] * g[t][j]);
    }
    
    return dist[B];
}


int main()
{
    cin >> n >> m;
    memset (g, 0, sizeof g);
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        scanf("%d%d%d",&a,&b,&v);
        g[a][b] = g[b][a] = max(g[a][b], (100.0 - v) / 100);
    }
    
    
    cin >> A >> B;
    
    double res = dijkstra();
    
    printf("%.8f\n", 100 / res);
    
    return 0;
    
}

920. 最优乘车

算法思路:

题目难点在建图,由于计算最少换乘次数, 所以认为一条线路上的车站之间距离均为1, 每条路线上有\(n\)个站点,站点之间有 \(C_{n}^{2}\) 条边, 且边权为1, 直接使用BFS即可。

题目拓展:

题目输入比较坑, 使用#include<sstream>里的stringstream,将一行数据通过string读入stringstream, 再分个读入int中(读入时自动通过空格分隔且转换类型)

#include <iostream>
#include <queue>
#include <cstring>
#include <sstream>

using namespace std;

const int N = 510;
const int M = 200000;
int g[N][N];
int dist[N];
int n,m;
int stop[N];    //所有站点

void bfs()
{
    memset(dist, -1, sizeof dist);
    dist[1] = 0;
    
    queue<int> q;
    q.push(1);
    
    while (q.size()){
        int u = q.front();
        q.pop();
        
        for (int i = 1; i <= n; i ++ )
            if(g[u][i] && dist[i] == -1){
                dist[i] = dist[u] + 1;
                q.push(i);
            }
    }
}


int main()
{
    cin >> m >> n;
    string ss;
    getline(cin, ss);   //读入回车
    
    for (int i = 1; i <= m; i ++ ){
        getline(cin,ss);    //读入ss
        //将ss读入scin;
        stringstream scin;
        scin << ss;
        
        int cnt = 0;
        int p;
        while (scin >> p){   //直接从scin读入p,同时转化为int
            stop[cnt ++] = p;
            //cout << p << endl;
        }
        
        //建图:
        for (int j = 0; j < cnt; j ++ )
            for (int k = j + 1; k < cnt; k ++ )
                g[stop[j]][stop[k]] = 1;
    }
    
    bfs();
    
    if (dist[n] == -1)
        puts("NO");
    else
        cout << max(dist[n] - 1, 0) << endl;
        
    return 0;
    
}

903. 昂贵的聘礼

算法思路:

难点在建图, 我们提前假设一个虚拟原点,该点到其它各物品的距离为该物品的初始原价, 物品相互之间的距离为对应优惠价格, 这样为题转化为从原点出发, 到1号点的最短距离。

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 110;
const int INF = 0x3f3f3f3f;

int dist[N];
int l[N];
int n,m;
int g[N][N];
int st[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] = 1;
        
        for (int j = 0; j <= n; j ++ )
            if (l[j] >= down && l[j] <= up)
                dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    
    return dist[1];
}



int main()
{
    cin >> m >> n;
    
    memset(g,INF, sizeof g);
    for (int i = 0; i <= n; i ++ )
        g[i][i] = 0;
    
    for (int i = 1; i <= n; i ++ ){
        int p, cnt;
        cin >> p >> l[i] >> cnt;
        
        g[0][i] = min(g[0][i], p);      //虚拟原点
        //其它点
        while (cnt -- ){
            int id, cost;
            cin >> id >> cost;
            g[id][i] = min(g[id][i], cost);
        }
    }
    
    
    int res = INF;
    //枚举l[1]的m区间:
    for (int i = l[1] - m; i <= l[1]; i ++ )
        res = min(res, dijkstra(i, i + m));
        
    cout << res << endl;
    
    return 0;
    
}

单源最短路的综合应用

AcWing 1135. 新年好

算法思路:

DFS + 最短路

先分别求出起点和其它5个亲戚到其它点的最短距离,由于拜访顺序不定且只有5个亲戚,所以我们可以DFS暴力枚举所有的拜访顺序(全排列)

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 50010;
const int M = 2e5 + 10;
int n ,m;
int h[N];
int e[M];
int ne[M];
int w[M];
int stop[N];
int st[N];
int idx;
int dist[6][N];
int ans = INF;
int num[N];         //记录排好序后某个亲戚家的对应地点编号
int last[N];        //记录对应1~5中对应亲戚家的编号

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

void dijkstra(int x)
{
    
    memset(st, 0 ,sizeof st);
    
    priority_queue<PII, vector<PII>, greater<PII> > heap;
    heap.push({0,stop[x]});
    dist[x][stop[x]] = 0;
    
    while (heap.size()){
        auto it = heap.top();
        heap.pop();
        
        int u = it.second;
        int distance = it.first;
        
        if (st[u])
            continue;
        st[u] = 1;
        
        for (int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            if (dist[x][j] > distance + w[i]){
                dist[x][j] = distance + w[i];
                heap.push({dist[x][j], j});
            } 
        }
    }
    
    return ;
}

int print()
{
    int res = 0;
    for (int i = 0; i < 5; i ++ )
        res += dist[last[i]][num[i + 1]];
    /*
    for (int i = 0; i <= 5; i ++ )
        cout << last[i]  << ' ';
    cout << endl;
    for (int i = 0; i <= 5; i ++ )
        cout << num[i] << ' ';
    cout << endl;
    
    cout << endl;
    */
    return res;
}

void dfs(int x)
{
    if (x > 5){
        ans = min(ans,print());
        return ;
    }else{
        for (int i = 1; i <= 5; i ++ ){
            if (!st[i]){
                last[x] = i;
                num[x] = stop[i];
                st[i] = 1;
                dfs(x + 1);
                st[i] = 0;
            }
        }
    }
}


int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    
    stop[0] = 1;
    for (int i = 1; i <= 5; i ++ )
        scanf("%d",&stop[i]);
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        scanf("%d%d%d",&a,&b,&v);
        add(a, b, v);
        add(b, a, v);
    }
    
    memset(dist, 0x3f, sizeof dist);
    for (int i = 0; i <= 5; i ++ )
        dijkstra(i);
    
    last[0] = 0;
    num[0] = 1;
    memset(st, 0 ,sizeof st);
    dfs(1);
    
    cout << ans << endl;
    
    return 0;
}

AcWing 340. 通信线路

算法思路:

二分 + 最短路

题目中要求最大值的最小值,考虑二分,对于费用\(L_{i}\) 我们二分整个区间\([0,1000001]\),对于每次的\(mid\),我们求出从起点到终点最少经过多少条(\(x\))权值大于\(mid\)的边, 若\(x\)大于k, \(mid\)不可行,区间向右,否则向左.

注意点:

  1. 二分区间为\([0,1000001]\),因为存在费用为0或是无解情况
  2. 每次求最短路是可以认为大于\(mid\)的边权重为1,否则为0,这样\(dist[n]\)即为所求的结果
#include <iostream>
#include <cstring>
#include <queue>
#include <deque>

using namespace std;
const int INF = 0x3f3f3f3f;
typedef pair<int,int> PII;
const int N = 1010, M = 20010;
int n,m,k;
int h[M];
int ne[M];
int e[M];
int idx;
int w[M];
int dist[M];
bool st[M];
//deque<int> q;

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

int check(int x)
{
    memset(st, 0, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    priority_queue<PII,vector<PII>,greater<PII>>heap;
    heap.push({0,1});
    
    while (heap.size()){
        auto it = heap.top();
        heap.pop();
        
        int u = it.second;
        
        if (st[u])
            continue;
        st[u] = true;
        
        for (int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            int t = w[i] > x;
            if (dist[j] > dist[u] + t){
                dist[j] = dist[u] + t;
                heap.push({dist[j], j});
            }
        }
    }
    
    if (dist[n] > k)
        return 0;
    else
        return 1;
    
}

int main()
{
    cin >> n >> m >> k;
    memset(h, -1, sizeof h);
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, v;
        cin >> a >> b >> v;
        add(a, b, v);
        add(b, a, v);
    }
    
    int l = 0;
    int r = 1e6 + 1;
    
    while (l < r){
        int mid = l + r >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
        //cout << l << ' ' << r << endl;
    }
    
    if (r == 1e6 + 1)
        cout << -1 << endl;
    else 
        cout << r << endl;
    
    return 0;
}

AcWing 342. 道路与航线

算法思路:

Dijkstra + 拓扑排序
  1. 对于每条道路,将其分块处理,分成诸多联通块.对于每个联通块,内部是一个正权图,可以使用Dijkstra算法.
  1. 对于每条航线,根据题意,每条航线连接联通块,且无环,通过航线的连接,联通块直接形成拓扑图.

对于整个图,联通块之间拓扑排序,联通块内部Dijkstra.

#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 25010, M = 50000 * 3 + 10;

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

int id[M];              //id[i]表示i号城市所在的联通块
int idcnt;              //联通块的总数
vector<int> block[N];   //block[i]存储第i个联通块内的所有城市的编号
int d[M];               //存储每个联通块的入度
int n, mr, mp, S;
queue<int> q;
int dist[M];
int st[M];

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

void dfs(int u, int idd)
{
    id[u] = idd;
    block[idd].push_back(u);
    
    for (int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        if (!id[j])
            dfs(j, idd);
    }
}

void dijkstra(int x)
{
    priority_queue<PII,vector<PII>,greater<PII>>heap;
    for (auto it : block[x])                //这些点到S的距离未知,所以把它们都加入优先队列排序
        heap.push({dist[it], it});
        
    while (heap.size()){
        auto it = heap.top();
        heap.pop();
        
        int u = it.second;
        
        if (st[u])
            continue;
        st[u] = 1;
        
        for (int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            if (dist[j] > dist[u] + w[i]){
                dist[j] = dist[u] + w[i];
                if (id[j] == id[u]){        //在同一个联通块里,可以继续dijkstra
                    heap.push({dist[j], j});
                }
            }
            if (id[j] != id[u]){            //不在同一个联通块里,根据拓扑排序,航线砍掉
                d[id[j]] --;
                if (!d[id[j]])
                    q.push(id[j]);          //该联通块的入度为0
            }
        }
    }
}


void topsort()
{
    for (int i = 1; i <= idcnt; i ++ )
        if (!d[i])
            q.push(i);
    
    while (q.size()){
        int u = q.front();
        q.pop();
        dijkstra(u);
    }
}

int main()
{
    cin >> n >> mr >> mp >> S;
    
    memset(h, -1, sizeof h);
    
    for (int i = 1; i <= mr; i ++ ){
        int a, b, v;
        scanf("%d%d%d",&a,&b,&v);
        add(a, b, v);
        add(b, a, v);
    }
    
    for (int i = 1; i <= n; i ++ )
        if (!id[i])
            dfs(i, ++ idcnt);
    
    for (int i = 1; i <= mp; i ++ ){
        int a, b, v;
        scanf("%d%d%d",&a,&b,&v);
        add(a,b,v);
        d[id[b]] ++;
    }
    
    memset(dist,0x3f, sizeof dist);
    dist[S] = 0;
    topsort();
    
    for (int i = 1; i <= n; i ++ )
        if (dist[i] > INF / 2)
            puts("NO PATH");
        else
            printf("%d\n",dist[i]);

    return 0;
    
}
posted @ 2021-03-17 00:18  lhqwd  阅读(76)  评论(0)    收藏  举报