【图论】单源最短路

这篇具有很好参考价值的文章主要介绍了【图论】单源最短路。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

算法提高课笔记

单源最短路的建图方式

最短路问题可以分为以下两类:

  • 边权非负——朴素Dijkstra、堆优化Dijkstra
  • 有负权边——Bellman-Ford、SPFA

例题

热浪

原题链接

德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!!

他们的德克萨斯长角牛吃起来不错,可是它们并不是很擅长生产富含奶油的乳制品。

农夫John此时身先士卒地承担起向德克萨斯运送大量的营养冰凉的牛奶的重任,以减轻德克萨斯人忍受酷暑的痛苦。

John已经研究过可以把牛奶从威斯康星运送到德克萨斯州的路线。

这些路线包括起始点和终点一共有 T 个城镇,为了方便标号为 1 到 T。

除了起点和终点外的每个城镇都由 双向道路 连向至少两个其它的城镇。

每条道路有一个通过费用(包括油费,过路费等等)。

给定一个地图,包含 C 条直接连接 2 个城镇的道路。

每条道路由道路的起点 Rs,终点 Re 和花费 Ci 组成。

求从起始的城镇 Ts 到终点的城镇 Te 最小的总费用。

输入格式

第一行: 4 个由空格隔开的整数: T,C,Ts,Te;

第 2 到第 C+1 行: 第 i+1 行描述第 i 条道路,包含 3 个由空格隔开的整数: Rs,Re,Ci。

输出格式

一个单独的整数表示从 Ts 到 Te 的最小总费用。

数据保证至少存在一条道路。

数据范围

1 ≤ T ≤ 2500 1≤T≤2500 1T2500,
1 ≤ C ≤ 6200 1≤C≤6200 1C6200,
1 ≤ T s , T e , R s , R e ≤ T 1≤Ts,Te,Rs,Re≤T 1Ts,Te,Rs,ReT,
1 ≤ C i ≤ 1000 1≤Ci≤1000 1Ci1000

输入样例

7 11 5 4
2 4 2
1 4 3
7 2 2
3 4 3
5 7 5
7 3 3
6 1 1
6 3 4
2 4 3
5 6 3
7 2 1

输出样例

7
题意

板题

思路

写了个SPFA和堆优化的Dijkstra,详见代码

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 2510, M = 6200 * 2 + 10;

int n, m, S, T;
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 ++ ;
}

void spfa() // SPFA算法
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    dist[S] = 0;

    queue<int> q;
    q.push(S);
    st[S] = true;

    while (q.size())
    {
        int t = q.front();
        q.pop();

        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.push(j);
                    st[j] = true;
                }
            }
        }
    }
}

void dijkstra() // 堆优化Dijkstra
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    dist[S] = 0;

    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, S});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int distance = t.first, ver = t.second;

        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 main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m >> S >> T;

    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    // todo 下面的选一个就可以 //
    spfa();
    dijkstra();

    cout << dist[T] << endl;
}

信使

原题链接

战争时期,前线有 n 个哨所,每个哨所可能会与其他若干个哨所之间有通信联系。

信使负责在哨所之间传递信息,当然,这是要花费一定时间的(以天为单位)。

指挥部设在第一个哨所。

当指挥部下达一个命令后,指挥部就派出若干个信使向与指挥部相连的哨所送信。

当一个哨所接到信后,这个哨所内的信使们也以同样的方式向其他哨所送信。信在一个哨所内停留的时间可以忽略不计。

直至所有 n 个哨所全部接到命令后,送信才算成功。

因为准备充足,每个哨所内都安排了足够的信使(如果一个哨所与其他 k 个哨所有通信联系的话,这个哨所内至少会配备 k 个信使)。

现在总指挥请你编一个程序,计算出完成整个送信过程最短需要多少时间。

输入格式

第 1 行有两个整数 n 和 m,中间用 1 个空格隔开,分别表示有 n 个哨所和 m 条通信线路。

第 2 至 m+1 行:每行三个整数 i、j、k,中间用 1 个空格隔开,表示第 i 个和第 j 个哨所之间存在 双向 通信线路,且这条线路要花费 k 天。

输出格式

一个整数,表示完成整个送信过程的最短时间。

如果不是所有的哨所都能收到信,就输出-1。

数据范围

1 ≤ n ≤ 100 , 1≤n≤100, 1n100,
1 ≤ m ≤ 200 , 1≤m≤200, 1m200,
1 ≤ k ≤ 1000 1≤k≤1000 1k1000

输入样例

4 4
1 2 4
2 3 7
2 4 1
3 4 6

输出样例

11
题意

建图,指挥部在1

每个点可以给相邻点送信,求所有点都收到信的最短时间

如果有收不到信的,输出-1

思路

对于每个点来说:接收到信的时间等于他到指挥部的最短距离

因此可以分别求每个点的最短路径,求所有最短路径的最大值

如果有最短路径是正无穷,说明不能传过去,输出-1

下方代码使用Floyd算法

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int d[N][N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    
    cin >> n >> m;

    memset(d, 0x3f3f3f3f, sizeof d);
    for (int i = 1; i <= n; i ++ ) d[i][i] = 0;

    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        d[a][b] = d[b][a] = min(d[a][b], c);
    }

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

    int maxx = *max_element(d[1] + 1, d[1] + n + 1);
    if (maxx == 0x3f3f3f3f) cout << "-1";
    else cout << maxx;
}

香甜的黄油

原题链接

农夫John发现了做出全威斯康辛州最甜的黄油的方法:糖。

把糖放在一片牧场上,他知道 N 只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。

当然,他将付出额外的费用在奶牛上。

农夫John很狡猾,就像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。

他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。

农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。

给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。

数据保证至少存在一个牧场和所有牛所在的牧场连通。

输入格式

第一行: 三个数:奶牛数 N,牧场数 P,牧场间道路数 C。

第二行到第 N+1 行: 1 到 N 头奶牛所在的牧场号。

第 N+2 行到第 N+C+1 行:每行有三个数:相连的牧场A、B,两牧场间距 D,当然,连接是双向的。

输出格式

共一行,输出奶牛必须行走的最小的距离和。

数据范围

1 ≤ N ≤ 500 , 1≤N≤500, 1N500,
2 ≤ P ≤ 800 , 2≤P≤800, 2P800,
1 ≤ C ≤ 1450 , 1≤C≤1450, 1C1450,
1 ≤ D ≤ 255 1≤D≤255 1D255

输入样例

3 4 5
2
3
4
1 2 1
1 3 5
2 3 7
2 4 3
3 4 5

输出样例

8
题意

若干头牛分别在不同牧场,找到一个牧场使所有牛到该牧场的总距离之和最小

思路

求以每个点为起点,其余点到该点的最短路之和

输出最小值

以下代码为堆优化Dijkstra

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 810, M = 3000, INF = 0x3f3f3f3f;

int n, m, p;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[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 dijkstra(int start)
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    memset(st, false, sizeof st);
    dist[start] = 0;

    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, start});

    while (q.size())
    {
        auto t = q.top();
        q.pop();

        int distance = t.first, ver = t.second;
        
        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];
                q.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()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> p >> m;
    for (int i = 0; i < n; i ++ ) cin >> id[i];

    // 存图
    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    // 取所有res中的最小值
    int res = INF;
    for (int i = 1; i <= p; i ++ ) res = min(res, dijkstra(i));

    cout << res;
}

最小花费

原题链接

在 n 个人中,某些人的银行账号之间可以互相转账。

这些人之间转账的手续费各不相同。

给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问 A 最少需要多少钱使得转账后 B 收到 100 元。

输入格式

第一行输入两个正整数 n,m,分别表示总人数和可以互相转账的人的对数。

以下 m 行每行输入三个正整数 x,y,z,表示标号为 x 的人和标号为 y 的人之间互相转账需要扣除 z% 的手续费 ( z<100 )。

最后一行输入两个正整数 A,B。

数据保证 A 与 B 之间可以直接或间接地转账。

输出格式

输出 A 使得 B 到账 100 元最少需要的总费用。

精确到小数点后 8 位。

数据范围

1 ≤ n ≤ 2000 , 1≤n≤2000, 1n2000,
m ≤ 105 m≤105 m105

输入样例

3 3
1 2 1
2 3 2
1 3 3
1 3

输出样例

103.07153164
题意

n个人互相转账,给出每两个人转账的手续费,询问A最少发出去多少钱使得B可以收到100

思路

将每个人看做点,权重是能传过去的百分比(比如说手续费3%,这条边的权重就是0.97),问题就变成了
100 = d ( A ) ∗ w 1 ∗ w 2 ∗ … ∗ w n 100=d(A)*w_1*w_2*…*w_n 100=d(A)w1w2wn
想让 d ( A ) d(A) d(A) 最小,就要让 w i w_i wi 最大
所以就是求最短路算法(但是是让权重之积最大)

下方代码为朴素版Dijkstra

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 2010;

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

void dijkstra()
{
    dist[S] = 1;
    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] = true;

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

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;

    memset(g, 0, sizeof g);
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        double z = (100.0 - c) / 100;
        g[a][b] = g[b][a] = z;
    }

    cin >> S >> T;

    dijkstra();

    cout << fixed << setprecision(8) << 100 / dist[T];
}

最优乘车

原题链接

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 ≤ M ≤ 100 , 1≤M≤100, 1M100,
2 ≤ N ≤ 500 2≤N≤500 2N500

输入样例

3 7
6 7
4 7 3 6
2 1 3 5

输出样例

2
题意

给出多条公交车线路,求从起点到终点最少的换乘次数是多少

思路

这题的难度在建图上,如果有一条线路是 1->3->5->7,从1开始坐,不管做到3还是5还是7,都不需要换乘,因此可以将1向后面的所有点都连一条边

建完图直接BFS即可

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 510;

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

void bfs()
{
    memset(dist, 0x3f3f3f3f, sizeof dist);

    queue<int> q;
    q.push(1);
    dist[1] = 0;

    while (q.size())
    {
        int t = q.front();
        q.pop();

        for (int i = 1; i <= n; i ++ )
            if (g[t][i] && dist[i] > dist[t] + 1)
            {
                dist[i] = dist[t] + 1;
                q.push(i);
            }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    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) cout << "NO\n";
    else cout << max(dist[n] - 1, 0) << endl;
}

昂贵的聘礼

原题链接

年轻的探险家来到了一个印第安部落里。

在那里他和酋长的女儿相爱了,于是便向酋长去求亲。

酋长要他用 10000 个金币作为聘礼才答应把女儿嫁给他。

探险家拿不出这么多金币,便请求酋长降低要求。

酋长说:”嗯,如果你能够替我弄到大祭司的皮袄,我可以只要 8000 金币。如果你能够弄来他的水晶球,那么只要 5000 金币就行了。”

探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。

探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。

不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。

探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。

另外他要告诉你的是,在这个部落里,等级观念十分森严。

地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。

他是一个外来人,所以可以不受这些限制。

但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。

因此你需要在考虑所有的情况以后给他提供一个最好的方案。

为了方便起见,我们把所有的物品从 1 开始进行编号,酋长的允诺也看作一个物品,并且编号总是 1。

每个物品都有对应的价格 P,主人的地位等级 L,以及一系列的替代品 Ti 和该替代品所对应的”优惠” Vi。

如果两人地位等级差距超过了 M,就不能”间接交易”。

你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。

输入格式

输入第一行是两个整数 M,N,依次表示地位等级差距限制和物品的总数。

接下来按照编号从小到大依次给出了 N 个物品的描述。

每个物品的描述开头是三个非负整数 P、L、X,依次表示该物品的价格、主人的地位等级和替代品总数。

接下来 X 行每行包括两个整数 T 和 V,分别表示替代品的编号和”优惠价格”。

输出格式

输出最少需要的金币数。

数据范围

1 ≤ N ≤ 100 , 1≤N≤100, 1N100,
1 ≤ P ≤ 10000 , 1≤P≤10000, 1P10000,
1 ≤ L , M ≤ N , 1≤L,M≤N, 1L,MN,
0 ≤ X < N 0≤X<N 0X<N

输入样例

1 4
10000 3 2
2 8000
3 5000
1000 2 1
4 200
3000 2 1
4 200
50 2 0

输出样例

5250
题意

有个人想娶酋长的女儿,酋长需要指定数量金钱的聘礼,或者拿一样东西和少一点的金钱替代,这样东西的所有者需要指定的金钱换这样东西,或者拿另一样东西和少一点的金钱代替,以此类推…同时,所有者阶级超过指定数字的不能间接交易,现在给出所有东西的价值、替代方式和所有者阶级,求这个人最少用多少钱能娶到酋长的女儿

思路

题目很难读懂,读懂后发现难点还是在建图上,我们把每样物品看作一个点,根据样例可以建出以下图:
【图论】单源最短路,图论,图论,算法
此时设置一个虚拟源点,所有情况都从虚拟源点开始,得到以下图:
【图论】单源最短路,图论,图论,算法
从虚拟源点开始求到点1的最短路即可

那么阶级问题怎么考虑呢?

注意到阶级最高只有100,如果阶级限制是1的话,最多也只有100种划分情况,所以直接枚举所有的阶级划分就可以了

代码
#include <bits/stdc++.h>

using namespace std;


const int N = 110;

int n, m;
int w[N][N], level[N];
int dist[N];
bool st[N];

int dijkstra(int down, int up)
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    memset(st, 0, sizeof st);

    dist[0] = 0;
    for (int i = 1; i <= n; 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 ++ ) // 更新所有在阶级区间范围内且与t有邻边的点
            if (w[t][j] != 0x3f3f3f3f && level[j] >= down && level[j] <= up)
                dist[j] = min(dist[j], dist[t] + w[t][j]);
    }

    return dist[1];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> m >> n;
    
    // 存图
    memset(w, 0x3f3f3f3f, sizeof w);
    for (int i = 0; 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(price, w[0][i]);

        while (cnt -- )
        {
            int id, cost;
            cin >> id >> cost;
            w[id][i] = min(w[id][i], cost);
        }
    }

    int res = 0x3f3f3f3f;
    for (int i = level[1] - m; i <= level[1]; i ++ )
        res = min(res, dijkstra(i, i + m));

    cout << res;
}

单源最短路的综合应用

例题

新年好

原题链接

重庆城里有 n 个车站,m 条 双向 公路连接其中的某些车站。

每两个车站最多用一条公路连接,从任何一个车站出发都可以经过一条或者多条公路到达其他车站,但不同的路径需要花费的时间可能不同。

在一条路径上花费的时间等于路径上所有公路需要的时间之和。

佳佳的家在车站 1,他有五个亲戚,分别住在车站 a,b,c,d,e。

过年了,他需要从自己的家出发,拜访每个亲戚(顺序任意),给他们送去节日的祝福。

怎样走,才需要最少的时间?

输入格式

第一行:包含两个整数 n,m,分别表示车站数目和公路数目。

第二行:包含五个整数 a,b,c,d,e,分别表示五个亲戚所在车站编号。

以下 m 行,每行三个整数 x,y,t,表示公路连接的两个车站编号和时间。

输出格式

输出仅一行,包含一个整数 T,表示最少的总时间。

数据范围

1 ≤ n ≤ 50000 , 1≤n≤50000, 1n50000,
1 ≤ m ≤ 105 , 1≤m≤105, 1m105,
1 < a , b , c , d , e ≤ n , 1<a,b,c,d,e≤n, 1<a,b,c,d,en,
1 ≤ x , y ≤ n , 1≤x,y≤n, 1x,yn,
1 ≤ t ≤ 100 1≤t≤100 1t100

输入样例

6 6
2 3 4 5 6
1 2 8
2 3 3
3 4 4
4 5 5
5 6 2
1 6 7

输出样例

21
题意

找到一条至少经过指定五个点的最短路径

思路

我们只关心必须要经过的五个点,其他点有没有经过不重要

先用Dijkstra算出每两个点之间的最短距离,然后枚举五个点的所有情况取最小值即可

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 50010, M = 200010, INF = 0x3f3f3f3f;

int n, m;
int h[N], e[M], ne[M], w[M], idx;
int dist[6][N];
int source[6];
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 dijkstra(int start, int dist[]) // 堆优化Dijkstra
{
    memset(dist, 0x3f3f3f3f, N * 4); // 因为dist大小不固定所以不能用sizeof
    dist[start] = 0;
    memset(st, 0, sizeof st);

    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;
        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 dfs(int u, int start, int distance)
{
    if (u > 5) return distance;

    int res = INF;
    for (int i = 1; i <= 5; i ++ )
    {
        if (!st[i])
        {
            int next = source[i];
            st[i] = true;
            res = min(res, dfs(u + 1, i, distance + dist[start][next]));
            st[i] = false; // 恢复现场
        }
    }
    return res;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;
    source[0] = 1;
    for (int i = 1; i <= 5; i ++ ) cin >> source[i];

    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    for (int i = 0;i < 6; i ++ ) dijkstra(source[i], dist[i]);

    memset(st, false, sizeof st);
    cout << dfs(1, 0, 0);
}

通信线路

原题链接

在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站 Ai 和 Bi。

特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。

现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费 Li。

电话公司正在举行优惠活动。

农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。

农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。

求至少用多少钱可以完成升级。

输入格式

第 1 行:三个整数 N,P,K。

第 2…P+1 行:第 i+1 行包含三个整数 Ai,Bi,Li。

输出格式

包含一个整数表示最少花费。

若 1 号基站与 N 号基站之间不存在路径,则输出 −1。

数据范围

0 ≤ K < N ≤ 1000 , 0≤K<N≤1000, 0K<N1000,
1 ≤ P ≤ 10000 , 1≤P≤10000, 1P10000,
1 ≤ L i ≤ 1000000 1≤Li≤1000000 1Li1000000

输入样例

5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6

输出样例

4
题意

任选一条从 1 到 N 的路径,公司可以免费升级k条路线,只需要支付除了这k条路线之外的最大值,求这个值的最小值

思路

一条路线的权重可以直接理解成第k+1长的边权,如果边数小于等于k,则该路线的权重是0

于是想到 二分(使用二分的条件是所求的答案是某个分界点,分界点的一边满足性质,另一边不满足性质)

对于我们所求的答案x,性质为从1到N的路线上大于x的边数小于等于k,大于x的情况满足性质,小于x的情况不满足性质

如何来判断呢?

将大于x的边权设为1,其余设为0,那么大于x的边数就等于路径长度

于是问题就转化成了边权为0/1的最短路问题 -> 双端队列

(细节问题:注意二分边界是0到1e6+1,因为如果定为1e6的话,无解和解为1e6的情况都会返回1e6)

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 1010, M = 20010;

int n, m, k;

int h[N], e[M], ne[M], w[M], idx;
int dist[N];
deque<int> q;
bool st[N];

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

bool check(int bound)
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    memset(st, false, sizeof st);

    q.push_back(1);
    dist[1] = 0;

    while (q.size())
    {
        int t = q.front();
        q.pop_front();

        if (st[t]) continue;
        st[t] = true;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i], x = w[i] > bound;
            if (dist[j] > dist[t] + x)
            {
                dist[j] = dist[t] + x;
                if (!x) q.push_front(j);
                else q.push_back(j);
            }
        }
    }

    return dist[n] <= k;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m >> k;
    memset(h, -1, sizeof h);

    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    int l = 0, r = 1e6 + 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }

    if (r == 1e6 + 1) cout << -1 << endl;
    else cout << r << endl;
}

道路与航线

原题链接

农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。

他想把牛奶送到 T 个城镇,编号为 1∼T。

这些城镇之间通过 R 条道路 (编号为 1 到 R) 和 P 条航线 (编号为 1 到 P) 连接。

每条道路 i 或者航线 i 连接城镇 Ai 到 Bi,花费为 Ci。

对于道路, 0 ≤ C i ≤ 10000 0≤C_i≤10000 0Ci10000;然而航线的花费很神奇,花费 C i C_i Ci 可能是负数 ( − 10 , 000 ≤ C i ≤ 10000 ) (−10,000≤C_i≤10000) (10,000Ci10000)

道路是双向的,可以从 A i A_i Ai B i B_i Bi,也可以从 B i B_i Bi A i A_i Ai,花费都是 C i C_i Ci

然而航线与之不同,只可以从 A i A_i Ai B i B_i Bi

事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从 A i A_i Ai B i B_i Bi,那么保证不可能通过一些道路和航线从 B i B_i Bi 回到 A i A_i Ai

由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。

他想找到从发送中心城镇 S 把奶牛送到每个城镇的最便宜的方案。

输入格式

第一行包含四个整数 T,R,P,S。

接下来 R 行,每行包含三个整数(表示一个道路)Ai,Bi,Ci。

接下来 P 行,每行包含三个整数(表示一条航线)Ai,Bi,Ci。

输出格式

第 1…T 行:第 i 行输出从 S 到达城镇 i 的最小花费,如果不存在,则输出 NO PATH。

数据范围

1 ≤ T ≤ 25000 , 1≤T≤25000, 1T25000,
1 ≤ R , P ≤ 50000 , 1≤R,P≤50000, 1R,P50000,
1 ≤ A i , B i , S ≤ T 1≤Ai,Bi,S≤T 1Ai,Bi,ST

输入样例

6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10

输出样例

NO PATH
NO PATH
5
0
-95
-100
题意

图中有两种路线:

  • 道路:无向边,边权为正
  • 航线:有向边,边权可正可负

问把牛奶送到每个点的最短路径

思路

首先看到边权有负会想到spfa,但是这个做法会被卡

由题意可知,如果 a 到 b 有航线,那 b 就不可能到 a ,说明 a b 间也不存在道路

根据这个特点可以用 DFS 把整张图分成几个块,两个不同的块之间只有航线相连,每个块均连通(因此每个块中都不存在航线,因为航线连接的两个点是单向的),因为每个块中只有道路也就是只有正向边,所以每个块内可以用 Dijkstra ,块与块之间是有向边,所以可以用 拓扑序列

具体步骤:

  1. 先输入所有双向道路,然后DFS出所有连通块,记录两个数组:
    • id[]存储每个点属于哪个连通块
    • vector<int> block[]存储每个连通块有哪些点
  2. 输入所有航线,统计出每个连通块入度
  3. 按照拓扑序列依次出了每个连通块,先将所有入度为0的连通块编号加入队列中
  4. 每次从队头取出一个连通块的编号bid
  5. 将该block[bid]中的所有点加入堆中,然后对堆中所有点做Dijkstra
  6. 每次取出堆中距离最小的点ver
  7. 遍历ver的所有邻点j,如果id[j]==id[ver],如果j能被更新,就将j插入堆中,如果不相等,就说明该边是航线,则将id[j]这个连通块的入度减1,如果入度减为0,就将其插入拓扑序列的队列中
代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 25010, M = 150010, INF = 0x3f3f3f3f;

int n, mr, mp, S;
int id[N];
int h[N], e[M], ne[M], w[M], idx;
int dist[N], din[N];
vector<int> block[N];
int bcnt;
bool st[N];
queue<int> q;

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

void dfs(int u, int bid)
{
    id[u] = bid, block[bid].push_back(u);

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!id[j]) dfs(j, bid);
    }
}

void dijkstra(int bid)
{
    priority_queue<PII, vector<PII>, greater<PII> > heap;

    for (auto u : block[bid]) heap.push({dist[u], u});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second;
        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if (id[ver] != id[j] && -- din[id[j]] == 0) q.push(id[j]);
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                if (id[j] == id[ver]) heap.push({dist[j], j});
            }
        }
    }
}

void topsort()
{
    memset(dist, 0x3f3f3f, sizeof dist);
    dist[S] = 0;

    for (int i = 1; i <= bcnt; i ++ )
        if (!din[i]) q.push(i);

    while (q.size())
    {
        int t = q.front();
        q.pop();
        dijkstra(t);
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> mr >> mp >> S;
    memset(h, -1, sizeof h);

    while (mr -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    for (int i = 1; i <= n; i ++ )
    {
        if (!id[i])
        {
            bcnt ++ ;
            dfs(i, bcnt);
        }
    }

    while (mp -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        din[id[b]] ++ ;
        add(a, b, c);
    }

    topsort();

    for (int i = 1; i <= n; i ++ )
        if (dist[i] > INF / 2) cout << "NO PATH" << '\n';
        else cout << dist[i] << '\n';
}

最优贸易

原题链接

C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。

任意两个城市之间最多只有一条道路直接相连。

这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。

C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。

但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 C 国旅游。

当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。

设 C 国 n 个城市的标号从 1∼n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。

在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。

阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。

因为阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。

请你告诉阿龙,他最多能赚取多少旅费。

注意:本题数据有加强。

输入格式

第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。

第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。

接下来 m 行,每行有 3 个正整数,x,y,z,每两个整数之间用一个空格隔开。

如果 z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果 z=2,表示这条道路为城市 x 和城市 y 之间的双向道路。

输出格式

一个整数,表示答案。

数据范围

1 ≤ n ≤ 100000 , 1≤n≤100000, 1n100000,
1 ≤ m ≤ 500000 , 1≤m≤500000, 1m500000,
1 ≤ 各城市水晶球价格 ≤ 100 1≤各城市水晶球价格≤100 1各城市水晶球价格100

输入样例

5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2

输出样例

5
题意

有n个城市,每个城市水晶球的价值不一样,两个城市之间的道路可能是有向边可能是无向边,一个人要从1到n,在一个城市买水晶球另一个城市卖出,每个点可以经过多次且不一定要经过所有点,问最多赚多少

思路

dp

由题目可知,会在1-n的一条路径上先买入水晶球再卖出水晶球

我们设一个点x为分界点,在x前面(包括x)买入水晶球,在x后面(包括x)卖出水晶球,现在问题就转换成了在x前用最低的价格买入,在x后用最高的价格卖出

spfa 算出所有点前面的最低价格和后面的最高价格,然后依次遍历每一个点为分界点,用该点后面的最高价格减去前面的最低价格,输出这个值的最大值即可

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 100010, M = 2000010, INF = 0x3f3f3f3f;

int n, m;
int w[N];
int hs[N], ht[N], e[M], ne[M], idx;
int dmin[N], dmax[N];
bool st[N];

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

void spfa(int h[], int dist[], int type)
{
    queue<int> q;
    
    if (type == 0)
    {
        memset(dist, 0x3f3f3f3f, sizeof dmin);
        dist[1] = w[1];
        q.push(1);
    }
    else
    {
        memset(dist, -0x3f3f3f3f, sizeof dmax);
        dist[n] = w[n];
        q.push(n);
    }

    while (q.size())
    {
        int t = q.front();
        q.pop();

        st[t] = false;
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if ((type == 0 && dist[j] > min(dist[t], w[j])) || (type == 1 && dist[j] < max(dist[t], w[j])))
            {
                if (type == 0) dist[j] = min(dist[t], w[j]);
                else dist[j] = max(dist[t], w[j]);

                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> w[i];

    memset(hs, -1, sizeof hs);
    memset(ht, -1, sizeof ht);

    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(hs, a, b), add(ht, b, a);
        if (c == 2) add(hs, b, a), add(ht, a, b);
    }

    spfa(hs, dmin, 0);
    spfa(ht, dmax, 1);
    
    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, dmax[i] - dmin[i]);

    cout << res << '\n';
}

单源最短路的扩展应用

例题

选择最佳线路

原题链接

有一天,琪琪想乘坐公交车去拜访她的一位朋友。

由于琪琪非常容易晕车,所以她想尽快到达朋友家。

现在给定你一张城市交通路线图,上面包含城市的公交站台以及公交线路的具体分布。

已知城市中共包含 n 个车站(编号1~n)以及 m 条公交线路。

每条公交线路都是 单向的,从一个车站出发直接到达另一个车站,两个车站之间可能存在多条公交线路。

琪琪的朋友住在 s 号车站附近。

琪琪可以在任何车站选择换乘其它公共汽车。

请找出琪琪到达她的朋友家(附近的公交车站)需要花费的最少时间。

输入格式

输入包含多组测试数据。

每组测试数据第一行包含三个整数 n,m,s,分别表示车站数量,公交线路数量以及朋友家附近车站的编号。

接下来 m 行,每行包含三个整数 p,q,t,表示存在一条线路从车站 p 到达车站 q,用时为 t。

接下来一行,包含一个整数 w,表示琪琪家附近共有 w 个车站,她可以在这 w 个车站中选择一个车站作为始发站。

再一行,包含 w 个整数,表示琪琪家附近的 w 个车站的编号。

输出格式

每个测试数据输出一个整数作为结果,表示所需花费的最少时间。

如果无法达到朋友家的车站,则输出 -1。

每个结果占一行。

数据范围

n ≤ 1000 , m ≤ 20000 , n≤1000,m≤20000, n1000,m20000,
1 ≤ s ≤ n , 1≤s≤n, 1sn,
0 < w < n , 0<w<n, 0<w<n,
0 < t ≤ 1000 0<t≤1000 0<t1000

输入样例

5 8 5
1 2 2
1 5 3
1 3 4
2 4 7
2 5 6
2 3 5
3 5 1
4 5 1
2
2 3
4 3 4
1 2 3
1 3 4
2 3 2
1
1

输出样例

1
-1

题意

给出一张图,琪琪可以从指定多个点的任意一个点出发,到达指定的一个点,问最短路

思路

多个起点->虚拟源点

起初将所有起点入队即可

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 1010, M = 20010, INF = 0x3f3f3f3f;

int n, m, T;
int h[N], e[M], ne[M], w[M], idx;
int dist[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()
{
    int scnt; // 琪琪家旁边车站的个数
    cin >> scnt;

    memset(dist, 0x3f3f3f3f, sizeof dist);

    queue<int> q;
    while (scnt -- )
    {
        int u;
        cin >> u;
        dist[u] = 0;
        q.push(u); // 先将所有真实起点都入队
        st[u] = true;
    }

    while (q.size())
    {
        int t = q.front();
        q.pop();

        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.push(j);
                    st[j] = true;
                }
            }
        }
    }
}

int main()
{
    while (scanf("%d%d%d", &n, &m, &T) != -1)
    {
        memset(h, -1, sizeof h);
        idx = 0;

        while (m -- )
        {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, c);
        }

        spfa();

        if (dist[T] == INF) dist[T] = -1;
        cout << dist[T] << '\n';
    }
}

拯救大兵瑞恩

原题链接

1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。

瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图。

迷宫的外形是一个长方形,其南北方向被划分为 N 行,东西方向被划分为 M 列, 于是整个迷宫被划分为 N×M 个单元。

每一个单元的位置可用一个有序数对 (单元的行号, 单元的列号) 来表示。

南北或东西方向相邻的 2 个单元之间可能互通,也可能有一扇锁着的门,或者是一堵不可逾越的墙。

注意: 门可以从两个方向穿过,即可以看成一条无向边。

迷宫中有一些单元存放着钥匙,同一个单元可能存放 多把钥匙,并且所有的门被分成 P 类,打开同一类的门的钥匙相同,不同类门的钥匙不同。

大兵瑞恩被关押在迷宫的东南角,即 (N,M) 单元里,并已经昏迷。

迷宫只有一个入口,在西北角。

也就是说,麦克可以直接进入 (1,1) 单元。

另外,麦克从一个单元移动到另一个相邻单元的时间为 1,拿取所在单元的钥匙的时间以及用钥匙开门的时间可忽略不计。

试设计一个算法,帮助麦克以最快的方式到达瑞恩所在单元,营救大兵瑞恩。

输入格式

第一行有三个整数,分别表示 N,M,P 的值。

第二行是一个整数 k,表示迷宫中门和墙的总数。

接下来 k 行,每行包含五个整数, X i 1 , Y i 1 , X i 2 , Y i 2 , G i X_{i1},Y_{i1},X_{i2},Y_{i2},G_{i} Xi1,Yi1,Xi2,Yi2,Gi:当 G i ≥ 1 G_i≥1 Gi1 时,表示 ( X − i 1 , Y i 1 ) (X-{i1},Y_{i1}) (Xi1,Yi1) 单元与 ( X i 2 , Y i 2 ) (X_{i2},Y_{i2}) (Xi2,Yi2) 单元之间有一扇第 G i G_i Gi 类的门,当 G i = 0 G_i=0 Gi=0 时,表示 ( X i 1 , Y i 1 ) (X_{i1},Y_{i1}) (Xi1,Yi1) 单元与 ( X i 2 , Y i 2 ) (X_{i2},Y_{i2}) (Xi2,Yi2) 单元之间有一面不可逾越的墙。

接下来一行,包含一个整数 S,表示迷宫中存放的钥匙的总数。

接下来 S S S 行,每行包含三个整数 X i 1 , Y i 1 , Q i X_{i1},Y_{i1},Q_i Xi1,Yi1,Qi,表示 $(X_{i1},Y_{i1}) $单元里存在一个能开启第 Q i Q_i Qi 类门的钥匙。

输出格式

输出麦克营救到大兵瑞恩的最短时间。

如果问题无解,则输出 -1。

数据范围

∣ X i 1 − X i 2 ∣ + ∣ Y i 1 − Y i 2 ∣ = 1 , |Xi1−Xi2|+|Yi1−Yi2|=1, Xi1Xi2∣+Yi1Yi2∣=1,
0 ≤ G i ≤ P , 0≤Gi≤P, 0GiP,
1 ≤ Q i ≤ P , 1≤Qi≤P, 1QiP,
1 ≤ N , M , P ≤ 10 , 1≤N,M,P≤10, 1N,M,P10,
1 ≤ k ≤ 150 1≤k≤150 1k150

输入样例

4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0 
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2 
4 2 1

输出样例

14

题意

给出迷宫,其中有不能穿过的墙, 能穿过的路和需要钥匙才能穿过的门,有一些格子上有钥匙,每移动一格消耗时间1,求从左上到右下的最短路

思路

把迷宫的每一格看做一个点,每两个点之间能走就说明有边,先建图

用二进制的01表示当前身上有没有每一把钥匙的状态

dist[i][j]表示在第i个点,身上钥匙的状态是j时的最短路

然后跑一遍 双端队列BFS

如果该点有钥匙,就捡起来,更新dist的状态,新状态加入队头

然后看这个点的所有邻边:

  • 有门没钥匙
  • 没有门,可更新的话新状态加入队尾

代码

#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 11, M = 360, P = 1 << 10;

int n, m, k, p;
int h[N * N], e[M], w[M], ne[M], idx;
int g[N][N], key[N * N];
int dist[N * N][P]; // dist[i][j]表示在i位置有j钥匙的最短路距离
bool st[N * N][P];

set<PII> edges;

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

void build()
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            for (int u = 0; u < 4; u ++ )
            {
                int x = i + dx[u], y = j + dy[u];
                if (!x || x > n || !y || y > m) continue;
                int a = g[i][j], b = g[x][y];
                if (!edges.count({a, b})) add(a, b, 0); // 之前没处理过就在这两点之间加边
            }
}

int bfs()
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    dist[1][0] = 0;

    deque<PII> q;
    q.push_back({1, 0});

    while (q.size())
    {
        PII t = q.front();
        q.pop_front();

        if (st[t.first][t.second]) continue;
        st[t.first][t.second] = true;

        if (t.first == n * m) return dist[t.first][t.second];

        if (key[t.first]) // 该点有钥匙
        {
            int state = t.second | key[t.first]; // 状态加上该钥匙
            if (dist[t.first][state] > dist[t.first][t.second]) // 可以更新
            {
                dist[t.first][state] = dist[t.first][t.second];
                q.push_front({t.first, state});
            }
        }

        for (int i = h[t.first]; ~i; i = ne[i])
        {
            int j = e[i];
            if (w[i] && !(t.second >> w[i] - 1 & 1)) continue; // 有门没钥匙
            if (dist[j][t.second] > dist[t.first][t.second] + 1) // 没有阻碍的路
            {
                dist[j][t.second] = dist[t.first][t.second] + 1;
                q.push_back({j, t.second});
            }
        }
    }
    return -1;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m >> p >> k;

    for (int i = 1, t = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            g[i][j] = t ++ ;

    memset(h, -1, sizeof h);
    while (k -- )
    {
        int x1, x2, y1, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        int a = g[x1][y1], b = g[x2][y2];

        edges.insert({a, b}), edges.insert({b, a}); // 记录哪些边加过了
        if (c) add(a, b, c), add(b, a, c); // 有钥匙就在这两点之间连边
    }

    build();

    int s;
    cin >> s;
    while (s -- )
    {
        int x, y, c;
        cin >> x >> y >> c;
        key[g[x][y]] |= 1 << c - 1; // 钥匙加到该点上
    }

    cout << bfs() << '\n';
}

最短路计数

原题链接

给出一个 N 个顶点 M 条边的无向无权图,顶点编号为 1 到 N。

问从顶点 1 开始,到其他每个点的最短路有几条。

输入格式

第一行包含 2 个正整数 N,M,为图的顶点数与边数。

接下来 M 行,每行两个正整数 x,y,表示有一条顶点 x 连向顶点 y 的边,请注意可能有自环与重边。

输出格式

输出 N 行,每行一个非负整数,第 i 行输出从顶点 1 到顶点 i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出对 100003 取模后的结果即可。

如果无法到达顶点 i 则输出 0。

数据范围

1 ≤ N ≤ 105 , 1≤N≤105, 1N105,
1 ≤ M ≤ 2 × 105 1≤M≤2×105 1M2×105

输入样例

5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5

输出样例

1
1
1
2
4
题意

给出一张图,问从1到每个点的最短路条数

思路

这题求最短路的数量而不是最短路

bfs和Dijkstra每个点都只入一次队,出队时即为最短路,但spfa不能保证出队时是最短路,本题选用bfs / Dijkstra

当新的点最短路可以更新到比当前值小,那么最短路的数量就是新的点的源点的最短路数量

当新的点最短路可以更新到和当前值一样,那最短路的数量就加上新的点的源点的最短路数量

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 100010, M = 400010, mod = 100003;

int n, m;
int h[N], e[M], ne[M], idx;
int dist[N], cnt[N];

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

void bfs()
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    dist[1] = 0;
    cnt[1] = 1;

    queue<int> q;
    q.push(1);

    while (q.size())
    {
        int t = q.front();
        q.pop();

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + 1)
            {
                dist[j] = dist[t] + 1;
                cnt[j] = cnt[t];
                q.push(j);
            }
            else if (dist[j] == dist[t] + 1)
            {
                cnt[j] = (cnt[j] + cnt[t]) % mod;
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;
    memset(h, -1, sizeof h);

    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }

    bfs();

    for (int i = 1; i <= n; i ++ ) cout << cnt[i] << '\n';
}

观光

原题链接

“您的个人假期”旅行社组织了一次比荷卢经济联盟的巴士之旅。

比荷卢经济联盟有很多公交线路。

每天公共汽车都会从一座城市开往另一座城市。

沿途汽车可能会在一些城市(零或更多)停靠。

旅行社计划旅途从 S 城市出发,到 F 城市结束。

由于不同旅客的景点偏好不同,所以为了迎合更多旅客,旅行社将为客户提供多种不同线路。

游客可以选择的行进路线有所限制,要么满足所选路线总路程为 S 到 F 的最小路程,要么满足所选路线总路程仅比最小路程多一个单位长度。

【图论】单源最短路,图论,图论,算法
如上图所示,如果 S=1,F=5,则这里有两条最短路线 1→2→5,1→3→5,长度为 6;有一条比最短路程多一个单位长度的路线 1→3→4→5,长度为 7。

现在给定比荷卢经济联盟的公交路线图以及两个城市 S 和 F,请你求出旅行社最多可以为旅客提供多少种不同的满足限制条件的线路。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

每组数据第一行包含两个整数 N 和 M,分别表示总城市数量和道路数量。

接下来 M 行,每行包含三个整数 A,B,L,表示有一条线路从城市 A 通往城市 B,长度为 L。

需注意,线路是 单向的,存在从 A 到 B 的线路不代表一定存在从 B 到 A 的线路,另外从城市 A 到城市 B 可能存在多个不同的线路。

接下来一行,包含两个整数 S 和 F,数据保证 S 和 F 不同,并且 S、F 之间至少存在一条线路。

输出格式

每组数据输出一个结果,每个结果占一行。

数据保证结果不超过 109。

数据范围

2 ≤ N ≤ 1000 , 2≤N≤1000, 2N1000,
1 ≤ M ≤ 10000 , 1≤M≤10000, 1M10000,
1 ≤ L ≤ 1000 , 1≤L≤1000, 1L1000
1 ≤ A , B , S , F ≤ N 1≤A,B,S,F≤N 1A,B,S,FN

输入样例

2
5 8
1 2 3
1 3 2
1 4 5
2 3 1
2 5 3
3 4 2
3 5 4
4 5 3
1 5
5 6
2 3 1
3 2 1
3 1 10
4 5 2
5 2 7
5 2 7
4 1

输出样例

3
2
题意

给出一张图,求给定两个点之间的最短路和次短路条数之和(次短路需要比最短路长度严格多1,不满足该条件就没有次短路)

思路

用堆优化Dijkstra求出最短路次短路数量(同时记录具体值)

详细看下方代码注释文章来源地址https://www.toymoban.com/news/detail-632207.html

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 1010, M = 20010;

struct Ver
{
    int id, type, dist; // 第id个点,最or次短路,具体值
    bool operator> (const Ver &W) const{
        return dist > W.dist;
    }
};

int n, m, S, T;
int h[N], e[M], w[M], ne[M], idx;
int dist[N][2], cnt[N][2]; // dist表示最/次短路值 cnt表示最/次短路条数
bool st[N][2];

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

int dijkstra()
{
    memset(st, false, sizeof st);
    memset(dist, 0x3f3f3f3f, sizeof dist);
    memset(cnt, 0, sizeof cnt);

    dist[S][0] = 0, cnt[S][0] = 1;
    priority_queue<Ver, vector<Ver>, greater<Ver>> heap;
    heap.push({S, 0, 0});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.id, type = t.type, distance = t.dist, count = cnt[ver][type];

        if (st[ver][type]) continue;
        st[ver][type] = true;

        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j][0] > distance + w[i]) // 可以更新最短路
            {
                dist[j][1] = dist[j][0], cnt[j][1] = cnt[j][0]; // 更新次短路
                heap.push({j, 1, dist[j][1]});
                dist[j][0] = distance + w[i], cnt[j][0] = count; // 更新最短路
                heap.push({j, 0, dist[j][0]});
            }
            else if (dist[j][0] == distance + w[i]) cnt[j][0] += count; // 更新最短路条数
            else if (dist[j][1] > distance + w[i]) // 可以更新次短路
            {
                dist[j][1] = distance + w[i], cnt[j][1] = count; // 更新次短路
                heap.push({j, 1, dist[j][1]});
            }
            else if (dist[j][1] == distance + w[i]) cnt[j][1] += count; // 更新次短路条数
        }

    }
    int res = cnt[T][0];
    if (dist[T][0] + 1 == dist[T][1]) res += cnt[T][1];

    return res;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int cases;
    cin >> cases;
    while (cases -- )
    {
        cin >> n >> m;
        memset(h, -1, sizeof h);
        idx = 0;

        while (m -- )
        {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, c);
        }
        cin >> S >> T;
        
        cout << dijkstra() << '\n';
    }
}

到了这里,关于【图论】单源最短路的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【图论】单源最短路

    算法提高课笔记 最短路问题可以分为以下两类: 边权非负——朴素Dijkstra、堆优化Dijkstra 有负权边——Bellman-Ford、SPFA 热浪 原题链接 德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!! 他们的德克萨斯长角牛吃起来不错,可是它们并不是很擅长生产富含奶油的乳制品

    2024年02月14日
    浏览(27)
  • 【图论】单源最短路问题

    Dijkstra算法是一种单源最短路径算法,用于找出图中从一个源点到其他所有点的最短路径。该算法的原理是采用贪心策略,每次将距离源点最近的点加入到已确定最短路径的集合中,并更新其它节点的距离。具体实现过程如下: 初始化距离数组dist[],源点距离为0,其余点距离

    2024年02月13日
    浏览(26)
  • 【图论 单源最短路】100276. 最短路径中的边

    单源最短路 图论知识汇总 给你一个 n 个节点的无向带权图,节点编号为 0 到 n - 1 。图中总共有 m 条边,用二维数组 edges 表示,其中 edges[i] = [ai, bi, wi] 表示节点 ai 和 bi 之间有一条边权为 wi 的边。 对于节点 0 为出发点,节点 n - 1 为结束点的所有最短路,你需要返回一个长度

    2024年04月22日
    浏览(22)
  • 【单源最短路 图论】882. 细分图中的可到达节点

    视频算法专题 单源最短路 图论 给你一个无向图(原始图),图中有 n 个节点,编号从 0 到 n - 1 。你决定将图中的每条边 细分 为一条节点链,每条边之间的新节点数各不相同。 图用由边组成的二维数组 edges 表示,其中 edges[i] = [ui, vi, cnti] 表示原始图中节点 ui 和 vi 之间存在

    2024年04月10日
    浏览(30)
  • 第三章 图论 No.1单源最短路及其综合应用

    做乘法的最短路时,若权值=0,只能用spfa来做,相等于加法中的负权边 1129. 热浪 1129. 热浪 - AcWing题库 单源最短路,稀疏图,用堆优化Dijkstra即可,就是板子套了个背景 debug:由于是无向图,边的数量要开两倍。但是 w[N] 没改,debug了很久 所以 e[M], ne[M], w[M] ,只有 h[N] ,其他

    2024年02月14日
    浏览(33)
  • 第三章 图论 No.2单源最短路之虚拟源点,状压最短路与最短路次短路条数

    dp是特殊的最短路,是无环图(拓扑图)上的最短路问题 1137. 选择最佳线路 1137. 选择最佳线路 - AcWing题库 对于每组测试数据,该重置的数据要重置,我没有重置idx,导致TLE 处理反向建图,还有一种扩展做法:虚拟源点 设置虚拟源点,与每个起点之间连接边权为0的边 原问题

    2024年02月14日
    浏览(36)
  • 【算法】单源最短路径算法——Dijkstra算法

    迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。这是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用 贪心算法 的策略, 每次遍历到始点距离最

    2024年02月05日
    浏览(36)
  • 数据结构学习记录——图-最短路径问题(无权图单源最短路径算法、有权图单源最短路径算法、多源最短路径算法、Dijkstra(迪杰斯特拉)算法、Floyd算法)

    目录 问题分类  无权图单源最短路径算法 思路 伪代码 时间复杂度 代码实现(C语言) 有权图单源最短路径算法 Dijkstra(迪杰斯特拉)算法 伪代码  时间复杂度  代码实现(C语言) 多源最短路径算法 两种方法 Floyd算法 代码实现(C语言) 最短路径问题的抽象 在网络中,求

    2024年02月08日
    浏览(49)
  • C++算法:单源最短路径Dijkstra

    如果你有一份北京地图,想从中关村走到三元桥,那么怎样能找出实现这一目的的最短路径呢?一种可能的方法就是将这两点之间所有的路线都找出来,然后求出每条路线的距离,找出最短的路线。但是仔细想想我们就会发现这种办法几乎是不可行的,因为这样的路线太多了,

    2024年02月09日
    浏览(36)
  • 迪杰斯特拉算法 – 图的单源最短路径

    迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。迪杰斯特拉算法采

    2024年02月05日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包