最短路与环学习&总结

本文深入探讨了图论中的最短路径算法,包括Dijkstra、SPFA、Floyd算法及其优化,详细解析了它们的时间复杂度和适用场景。此外,介绍了最短路径树的概念和应用,如最短路径计数、分层图最短路,并提供了多个实例进行解释。文章还提及了K短路问题和求两对点最短路最大重合路径长度的方法。最后,讲解了如何利用DFS求环并给出了相关模板题。
摘要由CSDN通过智能技术生成

前言

  1. 之前的博客用富文本写的,现在(手动emm,顺带复习一下)转换过来。
  2. 不止最短路,还有求环啥的
  3. 因为是主要是针对自己用的,所以很多自己已经熟悉的东西就没敲板子了,比如说dijkstra的朴素算法。
  4. 参考博客
    1. 专题·最短路【including Dijkstra, SPFA,Floyd,传递闭包,二维最短路,分层图最短路,最短路计数……
    2. 解题报告:【kuangbin带你飞】专题四 最短路练习题
    3. 个人ACM模板总结

一、单源最短路

dijkstra算法

  1. 先上个图
    在这里插入图片描述
  2. 操作(与Prim很相似):dis数组初始化为infdis[s]=0(以下假设s为1)。然后每次循环找到没有标记而且 dis 值最小的点对它邻接的点进行松弛操作,同时标记该点(证明该点不可能再减小:它已经是能到达的点中dis最小的了,如果想让其他店对它松弛操作之后还能减小,那是不可能的!)每次松弛操作:if(dis[u]+w<dis[v]) dis[v]=dis[u]+w
  3. 注意不能处理带负权的图

堆优化模板(时间复杂度 O ( m log ⁡ m ) O(m\log m) O(mlogm))(找最小的dis以及其下标)

  1. 注意什么时候标记vis[i]=1,应该在遍历一个 i 之后标记,写在遍历前也不影响——总之要理解
  2. 代码
/*
1.题目链接:[P4779 【模板】单源最短路径(标准版)](https://www.luogu.com.cn/problem/P4779)
	a.注意是有向边 
*/
#include <bits/stdc++.h>
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
 
using namespace std;
const int maxn = 2e5 + 10;
const int inf = 2e9;
 
struct node {
    int to, w;
    node() {}
    node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
    int fi, se;
    pii() {}
    pii(int _fi, int _se) { fi = _fi, se = _se; }
    bool operator<(pii b) const { return fi > b.fi; }
};
priority_queue<pii> q;
int n, m, s;
int u, v, w;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
void dijkstra(int s) {
    for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0;
    dis[s] = 0;
    q.push(pii(0, s));
    while (!q.empty()) {
        pii now = q.top();
        q.pop();
        int d = now.fi, i = now.se;  //距离和下标
        if (vis[i])
            continue;  //之前标记过的点仍然在优先队列中取不出来很正常,到时候取出来
        vis[i] = 1;
        for (auto j : g[i]) {
            if (vis[j.to]) continue;
            //松弛操作
            if (d + j.w < dis[j.to]) {
                dis[j.to] = d + j.w;
                q.push(pii(dis[j.to], j.to));
            }
        }
    }
}
signed main() {
    read(n), read(m), read(s);
    for (int i = 1; i <= m; i++) {
        read(u), read(v), read(w);
        g[u].pb(node(v, w));
    }
    dijkstra(s);
    for (int i = 1; i <= n; i++) print(dis[i], ' ');
    return 0;
}
  1. 其他优化:参考oi-wiki
    1. 暴力: O ( n 2 + m ) O(n^2+m) O(n2+m)
    2. 堆: O ( m log ⁡ n ) O(m\log n) O(mlogn)
    3. 优先队列: O ( m log ⁡ m ) O(m\log m) O(mlogm)
    4. zkw线段树: O ( m log ⁡ n + n ) O(m\log n+n) O(mlogn+n)
    5. Fibonacci堆: O ( n log ⁡ n + m ) O(n\log n+m) O(nlogn+m)
    6. 主要在于维护整个区间内最小的值以及它的小标,堆优化和优先队列时间复杂度差不多。

SPFA

  1. 介绍:对bellman_ford算法的一个队列优化。可用于求单源最短路。(可以处理求含负权的最短路,但是不能处理含负环的最短路)。
  2. 操作:类似于 bfs ,每次检查可以更新当时没在队列中的点放进去继续等待更新,等一个都不能更新的时候就自动出来了。操作也简单。
  3. 时间复杂度:稀疏图 O ( k m ) O(km) O(km) k k k为常数。稠密图中退化到 O ( n m ) O(nm) O(nm)

模板(放入队列中的同时vis[s]=1,取出的同时vis[now]=0,更新所有可以更新的,更新的时候放入所有能放入的即vis[x]=0的x)

/*
1.题目链接:(和上面一个题)
*/
#include <bits/stdc++.h>
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
 
using namespace std;
const int maxn = 2e5 + 10;
const int inf = 2e9;
 
struct node {
    int to, w;
    node() {}
    node(int _to, int _w) { to = _to, w = _w; }
};
int n, m, s;
int u, v, w;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
void spfa() {
    for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0;
    dis[s] = 0;
    queue<int> q;
    q.push(s), vis[s] = 1;
    while (!q.empty()) {
        int x = q.front();
        q.pop(), vis[x] = 0;  //毕竟一般也不会有自环
        for (auto i : g[x]) {
            /* if
             * (vis[i.to])continue;不能这样,这个算法在队列中不代表不能够优化!——应该放在下面!*/
            if (dis[x] + i.w < dis[i.to]) {
                dis[i.to] = dis[x] + i.w;
                if (!vis[i.to]) q.push(i.to), vis[i.to] = 1;
            }
        }
    }
}
signed main() {
    read(n), read(m), read(s);
    for (int i = 1; i <= m; i++) {
        read(u), read(v), read(w);
        g[u].pb(node(v, w));
    }
    spfa();
    for (int i = 1; i <= n; i++) print(dis[i], ' ');
    return 0;
}

二、全源最短路

Floyd

  1. 介绍:可以处理求含负权的最短路,但是不能处理含负环的最短路。(主要是负环没有最短路emmm)
  2. 拓展:传递闭包,最长/短路,最小/“大”瓶颈路,判正负环。最小环?

模板(无脑三重循环)

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

三、传递闭包

  1. 介绍mp[i][j]=1表示存在 i i i j j j的路径。floyd求出所有的mp[i][j]

模板

for (int k = 1; k <= n; k++)
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) mp[i][j] |= mp[i][k] & mp[k][j];

四、最短路径树(应用)

  1. 概念:最短路构成的一棵树(一个概念罢了,拓展学不是很强)。

最短路径树计数

  1. 题目链接黑暗城堡 LibreOJ - 10064
  2. 题意 n ( 1 ≤ n ≤ 1000 ) n(1\le n\le 1000) n(1n1000)个点,m条无向边,每条边长度为 l i ( 1 ≤ l ≤ 200 ) l_i(1\le l\le200) li1l200,求不同最短路径树(最开始从1出发)的数量(结果对 2 31 − 1 2^{31}-1 2311取余)。
  3. 题解:求出每个点的前一个节点的数量(满足对短路,即dis[u]+w==dis[v],dis[i]表示节点s到i的最短距离),最后相乘取余。
  4. 操作:在松弛操作的时候操作(spfa的话建议用第二种操作,第一种或多或少有些问题!)或者在求出所有点的最短路之后操作。

模板(最短路径数计数模板题):

/*
1.题目就是上面的【黑暗城堡】
*/
/*操作1:在松弛操作的地方做修改*/
#include <bits/stdc++.h>
#define int long long
#define read(x) scanf("%lld", &x)
#define print(a, c) printf("%lld%c", a, c)
// #define ll long long
#define pb push_back
 
using namespace std;
const int maxn = 1e3 + 10;
const int maxm = 1e6 + 10;
const int inf = 2147483647;  // 1<<31-1
 
struct node {
    int to, w;
    node() {}
    node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
    int fi, se;
    pii() {}
    pii(int _fi, int _se) { fi = _fi, se = _se; }
    bool operator<(pii b) const { return fi > b.fi; }
};
priority_queue<pii> q;
int n, m;
int u, v, w;
vector<node> g[maxn];
int dis[maxn], vis[maxn], pre[maxn];
void dijkstra() {
    for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0, pre[i] = 0;
    dis[1] = 0, pre[1] = 1;
    q.push(pii(0, 1));
    while (!q.empty()) {
        pii now = q.top();
        q.pop();
        int d = now.fi, i = now.se;  //距离和下标
        if (vis[i])
            continue;  //之前标记过的点仍然在优先队列中取不出来很正常,到时候取出来
        vis[i] = 1;
        for (auto j : g[i]) {
            if (vis[j.to]) continue;
            //松弛操作
            if (d + j.w == dis[j.to]) pre[j.to]++;  //操作1第一步
            if (d + j.w < dis[j.to]) {
                dis[j.to] = d + j.w;
                q.push(pii(dis[j.to], j.to)), pre[j.to] = 1;  //操作一第二步
            }
        }
    }
}
signed main() {
    read(n), read(m);
    for (int i = 1; i <= m; i++) {
        read(u), read(v), read(w);
        g[u].pb(node(v, w)), g[v].pb(node(u, w));
    }
    dijkstra();
    int ans = 1;
    // for (int i = 1; i <= n; i++) cout << i << ":::" << pre[i] << endl;
    for (int i = 1; i <= n; i++) ans = (ans * pre[i]) % inf;
    print(ans, '\n');
    return 0;
}

去掉途中一条边之后最短路径树大小(dis之和)是否有变化

  1. 题意 n n n个城市 ( n ≤ 100 ) (n\le 100) n100 m m m条无向边,每两个城市之间都走最小路。从小到大打印所有公路序号,这些公路满足:去掉之后至少有两个城市之间的路程变大了。
  2. 题解:暴力每个起点,分别建最短路径树,然后枚举这树上的每条边,不经过这条边建一颗最短路径树,求出该起点到其他每个点的最短路径和,如果大于没去掉这条边之前的值,一定满足条件。说明,枚举任意起点的时候可以建任意一颗最短路径树,因为如果存在多颗最短路径树,同时枚举的某条边如果可以替代,那么他们是可以相互替代的,去掉任何一颗都不会改变最短路径和!!!
  3. 代码:就附上了,知道思路即可,做法仍是最短路+建最短路径树。

暂时就以上两个应用吧,毕竟自己没遇到过

五、最短路计数

  1. 概念:字面意思。
  2. 操作:与最短路径树的操作几乎一样,只需要做一些改动:pre初始化为0;pre[s]=1;然后每次遇到dis[u]==dis[v]的时候pre[v]+=pre[u];松弛操作的时候pre[v]=pre[u]就ok了。
  3. 对比最短路径树:pre初始化为0;pre[s]=1;然后每次遇到dis[u]==dis[v]的时候pre[v]++;松弛操作的时候pre[v]=1就ok了。
  4. 例题P1608 路径统计
    1. 题意:n 个点,最开始在1,要到达n。问最短路径是多少,有多少条。如果一条都没有,就打印no。 n ≤ 2000 , m ≤ n ∗ ( n − 1 ) n\le 2000,m\le n*(n-1) n2000,mn(n1),保证无自环,不保证无重边,另外所有路是单向的!——重边取最小值,而不是如果想等的有 x 条就表示这里有 x 种走法。

模板

void dijkstra() {
    for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0, pre[i] = 0;
    dis[1] = 0, pre[1] = 1;
    q.push(pii(0, 1));  //小顶堆
    while (!q.empty()) {
        pii now = q.top();
        q.pop();
        int d = now.fi, i = now.se;  //距离和下标
        if (vis[i])
            continue;  //之前标记过的点仍然在优先队列中取不出来很正常,到时候取出来
        vis[i] = 1;
        for (auto j : g[i]) {
            if (vis[j.to]) continue;
            //松弛操作
            if (d + j.w == dis[j.to]) pre[j.to] += pre[i];  //操作一第一步
            if (d + j.w < dis[j.to]) {
                dis[j.to] = d + j.w;
                q.push(pii(dis[j.to], j.to)),
                    pre[j.to] = pre[i];  //操作一第二步
            }
        }
    }
    // dis[i]==inf,或者说pre[n]=0时表示无路径
    (dis[n] == inf) ? puts("No answer")
                    : (print(dis[n], ' '), print(pre[n], '\n'));
}

六、分层图最短路(用分层图的几种情况)

  1. 参考博客
    1. 图论之分层图最短路总结 与经典例题
    2. 专题·最短路【including Dijkstra, SPFA,Floyd,传递闭包,二维最短路,分层图最短路,最短路计数……
  2. 介绍:只是在建图的时候不一样,除此之外就是跑个普通最短路。当然,难的也就是建图。

用分层图的几种情况

  1. k 个不同集合的边。将每个集合建一层图,然后第 k+1 层建一个虚图(用于连接每一层,价值根据题意)。如小雨坐地铁。

  2. 有 k 个机会免费走一条路。建 k+1 个相同的图,每层之间用有边的点连接起来,代价为0。没走一层表示用了一次机会。如P4568 [JLOI2011]飞行路线。

  3. 有k个机会逆向行驶。建 k+1 个相同的图,每层之间用有边的两点之间的逆向边连接,权值不变,没走一层表示用了一次机会(这里应该必须是有向图罢,不然没啥意义)。

例题1:小雨坐地铁

  1. 题目描述 n ( 1 − 1000 ) n(1-1000) n11000个车站, m ( 1 − 500 ) m(1-500) m1500条地铁线。每次经过第 i 条地铁线需要花费 a i ( 1 − 100 ) a_i(1-100) ai1100,在这条地铁线上,每经过一个车站多花费 b i ( 1 − 100 ) b_i(1-100) bi1100,每条线上有 c i ( 1 − n ) c_i(1-n) ci1n个车站(按顺序依次输入沿途站,注意是双向的)。
  2. 题解:情况1。注意进入第 m+1 层的时候花费 0 ,从 m+1 层进入的时候才花费 a x ax ax(相当于从nm+s到nm+t)。多读题,图别检错;注意建图之后点的总数。
  3. 代码
#include <bits/stdc++.h>
#define int long long
#define read(x) scanf("%lld", &x)
#define print(a, c) printf("%lld%c", a, c)
#define pb push_back
#define mst(a, x) memset(a, x, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int maxn = 500 * 1000 + 1000 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9;
struct node {
    int to, w;
    node() {}
    node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
    int fi, se;  // dis,id
    pii() {}
    pii(int _fi, int _se) { fi = _fi, se = _se; }
    bool operator<(pii b) const {
        return fi > b.fi;
    }  //平常的就大顶堆,否则小顶堆。这里小顶堆
};
int n, m, s, t;
int a, b, c;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
priority_queue<pii> q;
 
void dijkstra(int s) {
    for (int i = 1; i <= n * m + n; i++) dis[i] = inf;
    dis[s] = 0, vis[s] = 1;
    q.push(pii(0, s));
    while (!q.empty()) {
        pii now = q.top();
        q.pop();
        int x = now.se, w = now.fi;
        for (auto i : g[x]) {
            if (i.w + dis[x] < dis[i.to]) {
                dis[i.to] = i.w + dis[x], vis[i.to] = 1;
                q.push(pii(dis[i.to], i.to));
            }
        }
    }
}
signed main() {
    read(n), read(m), read(s), read(t);
    for (int i = 1; i <= m; i++) {
        read(a), read(b), read(c);
        int now, pre;
        //建第k张图
        for (int j = 1; j <= c; j++) {
            read(now);
            if (j > 1) {
                int u = (i - 1) * n + pre, v = (i - 1) * n + now, w = b;
                g[u].pb(node(v, w));
                g[v].pb(node(u, w));
            }
            //连虚图(相当于中转站)
            int u = (i - 1) * n + now, v = n * m + now, w = a;
            g[u].pb(node(v, 0));  //进入需要0
            g[v].pb(node(u, w));  //出来代表从一条线进入新的一条线(每次都要花钱)
            pre = now;
        }
    }
    dijkstra(n * m + s);  //从n*m+t出来
    // for (int i = 1; i <= 10; i++) print(dis[i], ' ');
    // cout << endl;
    if (dis[n * m + t] == inf)
        puts("-1");
    else
        print(dis[n * m + t], '\n');
    return 0;
}

题目2:P4568 [JLOI2011]飞行路线

  1. 题目链接P4568 [JLOI2011]飞行路线
  2. 题意 n ( 2 − 1 e 4 ) n(2-1e4) n21e4个点, m ( 1 − 5 e 4 ) m(1-5e4) m15e4条无向带权边(权值大小在 0 − 1 e 3 0-1e3 01e3),可以最多坐 k ( 0 − 10 ) k(0-10) k010次免费航班。问从 s 到 t 的最小花费。
  3. 题解:情况2 。难点在建图上
  4. 代码
#include <bits/stdc++.h>
#define int long long
#define read(x) scanf("%lld", &x)
#define print(a, c) printf("%lld%c", a, c)
#define pb push_back
#define mst(a, x) memset(a, x, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int maxn = 1e4 * 12 + +10;
const int mod = 1e9 + 7;
const int inf = 1e9;
struct node {
    int to, w;
    node() {}
    node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
    int fi, se;  // dis,id
    pii() {}
    pii(int _fi, int _se) { fi = _fi, se = _se; }
    bool operator<(pii b) const {
        return fi > b.fi;
    }  //正常为大顶堆,不正常为小顶堆
};
int n, m, k, s, t;
int u, v, w;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
priority_queue<pii> q;
 
void dijkstra(int s) {
    for (int i = 0; i <= n * k + n; i++) dis[i] = inf;
    dis[s] = 0, vis[s] = 1;
    q.push(pii(0, s));
    while (!q.empty()) {
        pii now = q.top();
        q.pop();
        int x = now.se, w = now.fi;
        for (auto i : g[x]) {
            if (i.w + dis[x] < dis[i.to]) {
                dis[i.to] = i.w + dis[x], vis[i.to] = 1;
                q.push(pii(dis[i.to], i.to));
            }
        }
    }
}
signed main() {
    read(n), read(m), read(k);
    read(s), read(t);
    for (int i = 1; i <= m; i++) {
        read(u), read(v), read(w);
        // 0层图
        g[u].pb(node(v, w)), g[v].pb(node(u, w));
        for (int j = 1; j <= k; j++) {
            // 1~k层图
            g[j * n + u].pb(node(j * n + v, w));
            g[j * n + v].pb(node(j * n + u, w));
            //上一层与这一层连接
            g[(j - 1) * n + u].pb(node(j * n + v, 0));
            g[(j - 1) * n + v].pb(node(j * n + u, 0));
        }
    }
    //传到n*k+t,最后直接取dis[n*k+t];
    for (int i = 1; i <= k; i++) g[(i - 1) * n + t].pb(node(i * n + t, 0));
    dijkstra(s);  //从n*m+t出来
    // for (int i = 0; i <= 10; i++) print(dis[i], ' ');
    // cout << endl;
    print(dis[n * k + t], '\n');
    return 0;
}
/*
5 6 1
0 4
0 1 5
1 2 5
2 3 5
3 4 5
2 3 3
0 2 100
*/

情况3:与情况2的差别只在建图,有这个意识就好。

  1. ps:也不止与这些应用。

七、k短路(模板题)

  1. 题目链接Remmarguts’ Date POJ - 2449
  2. 题意:N ( 1 ≤ N ≤ 1000 ) (1\le N\le 1000) (1N1000)个点,M ( 1 ≤ M ≤ 1 e 5 ) (1\le M\le 1e5) (1M1e5)条有向带权边,问点 s 到 t 的第 k $ ( 1 ≤ k ≤ 1000 ) (1\le k\le 1000) (1k1000)短路的长度。
  3. 题解k短路模板题。首先建一个反向路,dis[i]表示到 t 的最短路,然后 bfs ,得到一次 t 记一次数。bfs 具体操作见代码。
  4. 注意
    1. s 可能到不了 t。
    2. s 到 t可能没有 k 条路(s-t的任意路径上有环的话,s 到 t 的路可以视作有无数条)。
    3. s==t的时候 k 要加上1 ,因为 bfs 的时候最开始的 s 被算作 t(长度为 0 )。

模板1

#include <string.h>
 
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
// #define int long long
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
#define mst(a, x) memset(a, x, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9;
struct node {
    int to, w;
    node() {}
    node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
    int i, dis, dis1;
    pii() {}
    pii(int _i, int _dis, int _dis1) { i = _i, dis = _dis, dis1 = _dis1; }
    bool operator<(pii b) const { return dis + dis1 > b.dis + b.dis1; }
};
 
int n, m, s, t, k;
int u, v, w;
vector<node> g[maxn], re[maxn];
int dis[maxn], vis[maxn];
void spfa(int s) {
    for (int i = 0; i <= n; i++) dis[i] = inf;
    dis[s] = 0;
    queue<int> q;
    q.push(s), vis[s] = 1;
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        vis[x] = 0;
        // sbpoj,c++11都不支持
        for (int j = 0; j < re[x].size(); j++) {
            node i = re[x][j];
            if (i.w + dis[x] < dis[i.to]) {
                dis[i.to] = i.w + dis[x];
                if (vis[i.to] == 0) q.push(i.to), vis[i.to] = 1;
            }
        }
    }
}
priority_queue<pii> q;
void bfs(int s) {
    q.push(pii(s, 0, 0));
    int cnt = 0;
    while (!q.empty()) {
        pii now = q.top();
        q.pop();
        if (now.i == t) {
            cnt++;
            if (cnt == k) {
                print(now.dis, '\n');
                return;
            }
        }
        for (int j = 0; j < g[now.i].size(); j++) {
            node i = g[now.i][j];
            q.push(pii(i.to, now.dis + i.w, dis[i.to]));
        }
    }
    puts("-1");  //这是什么情况?——毕竟单向路径,可能s->t都没有k条路
}
signed main() {
    read(n), read(m);
    for (int i = 1; i <= m; i++) {
        read(u), read(v), read(w);
        g[u].pb(node(v, w));
        re[v].pb(node(u, w));
    }
    read(s), read(t), read(k);
    spfa(t);
    if (dis[s] == inf)
        puts("-1");
    else {
        if (s == t) k++;  //为啥。看bfs哪里。s也被视作t了,这个t不算
        bfs(s);
    }
    return 0;
}
/*
input:::
2 2
1 2 5
2 1 4
1 2 2
output:::
14
*/

模板2(要求输出前 k 小的数的话)

#include <bits/stdc++.h>
// #define int long long
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
#define mst(a, x) memset(a, x, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9;
struct node {
    int to, w;
    node() {}
    node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
    int i, dis, dis1;
    pii() {}
    pii(int _i, int _dis, int _dis1) { i = _i, dis = _dis, dis1 = _dis1; }
    bool operator<(pii b) const { return dis + dis1 > b.dis + b.dis1; }
};
 
int n, m, k;
int u, v, w;
vector<node> g[maxn], re[maxn];
vector<int> ans;
int dis[maxn], vis[maxn];
void spfa(int s) {
    for (int i = 0; i <= n; i++) dis[i] = inf;
    dis[s] = 0;
    queue<int> q;
    q.push(s), vis[s] = 1;
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        vis[x] = 0;
        for (int j = 0; j < re[x].size(); j++) {
            node i = re[x][j];
            if (i.w + dis[x] < dis[i.to]) {
                dis[i.to] = i.w + dis[x];
                if (vis[i.to] == 0) q.push(i.to), vis[i.to] = 1;
            }
        }
    }
}
priority_queue<pii> q;
void bfs(int s) {
    q.push(pii(s, 0, 0));
    int cnt = 0;
    while (!q.empty()) {
        pii now = q.top();
        q.pop();
        if (now.i == 1) {
            cnt++;
            ans.pb(now.dis);
            if (cnt == k) return;
        }
        for (int j = 0; j < g[now.i].size(); j++) {
            node i = g[now.i][j];
            q.push(pii(i.to, now.dis + i.w, dis[i.to]));
        }
    }
    while (ans.size() < k) ans.pb(-1);
}
signed main() {
    read(n), read(m), read(k);
    for (int i = 1; i <= m; i++) {
        read(u), read(v), read(w);
        g[u].pb(node(v, w));
        re[v].pb(node(u, w));
    }
    spfa(1);
    if (dis[n] == inf)
        for (int i = 1; i <= k; i++) ans.pb(-1);
    else {
        if (n == 1) k++;  //为啥。看bfs哪里。s也被视作t了,这个t不算
        bfs(n);
    }
    for (auto i : ans) print(i, '\n');
    return 0;
}
/*
input:::
5 8 7 
5 4 1 
5 3 1 
5 2 1 
5 1 1 
4 3 4 
3 1 1 
3 2 1 
2 1 1 
output:::
1 
2 
2 
3 
6 
7 
-1 
*/

八、求两对点的最短路的最大重合路径长度(遇到再说罢)

写在最后

现在可能还欠缺的知识:最小环,求两队点的最短路的最大重合路径长度。另一个博客独有的?判正环,负环,“最大瓶颈路”/最小瓶颈路,差分约束等等。还挺多没涉及到的emmm。恕我直言,凭我的废物属性,大概要三五个月下一轮学习的时候才会把两个博客整合(+补充欠缺的知识emmm)。现在拉拉进度罢先。

九、其他相关操作

模板1:求环(DFS求环模板题)

  1. 题目链接F. Forest Program
  2. 题意:n 个点,m 条无向边。无自环无重边,且每条边最多被一个环公用。求能得到的森林种类数。
  3. 题解:每个环至少删去一条边,其他不成环的边可删可不删。主要是求环
    1. 求环:DFS求环,从一个点出发,沿途点记录深度,如果DFS的时候遇到深度更小且不为-1的点则记录环(环的大小dep[x]-dep[i]+1)。
  4. 代码
#include <bits/stdc++.h>
// #define int long long
#define ll long long
#define pii pair<int, int>
#define x first
#define y second
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 5e5 + 10;
const int mod = 998244353;
int n, m, u, v;
vector<int> g[N], vec, ans;
int fa[N], dep[N];
int find(int x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) {
    // x = find(x), y = find(y);
    // if (x != y) fa[x] = fa[y];
    int fx = find(x), fy = find(y);
    fa[fx] = fy;
}
void dfs(int x, int Fa) {
    // cout << "### " << x << " " << Fa << endl;
    for (auto i : g[x]) {
        if (i == Fa) continue;  //无重边
        if (dep[i] != -1) {
            if (dep[i] < dep[x]) ans.push_back(dep[x] - dep[i] + 1);//自己模拟一遍,dep[i]!=-1不止是dep[i]<dep[x],还有可能从i遍历到x,那么swap(i,x),dep[i]<dep[x]。
            continue;
        }
        dep[i] = dep[x] + 1;
        dfs(i, x);
    }
}
ll Bit[N];
void init(int n) {
    Bit[0] = 1;
    for (int i = 1; i <= n; i++) Bit[i] = Bit[i - 1] * 2 % mod;
}
signed main() {
    init(N - 1);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) fa[i] = i;
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &u, &v);
        // cout << ">>>" << u << " " << v << endl;
        g[u].push_back(v), g[v].push_back(u);
        // merge(u, v);
        if (find(u) != find(v)) merge(u, v);
    }
    for (int i = 1; i <= n; i++) vec.push_back(find(i));
    sort(vec.begin(), vec.end());
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
    memset(dep, -1, sizeof(dep));
    for (auto i : vec) {
        // dbg(i);
        // i=2;
        dep[i] = 0;
        dfs(i, 0);
    }
    // for (auto i : ans) cout << i << " ";
    // cout << endl;
    ll sum = 1, cnt = m;
    for (auto i : ans)
        cnt -= 1LL * i, sum = sum * ((Bit[i] - 1LL) % mod + mod) % mod;
    // dbg(sum), dbg(cnt);
    sum = sum * Bit[cnt] % mod;
    // cout << ">>>";
    cout << sum << endl;
    return 0;
}
/*
Examples
inputCopy
3 3
1 2
2 3
3 1
outputCopy
7
inputCopy
6 6
1 2
2 3
3 1
2 4
4 5
5 2
outputCopy
49
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值