最短路总结
《好博客》
链接:https://blog.csdn.net/m0_50564748/article/details/123143604
最全的最短路问题综合
《Dijkstra总结》
《为什么Dijlstra不能用在有负权边的图中》
Dijkstra算法其只能用在dist不断变大(或不断变小,即要单调)的情况下;
这是由于算法本身的思想决定的:
《变式(来自大佬的博客,我只是添加上我认为的解释)》
由于dist[]对于未确定最小距离的点(还未加入S集合,还在V-S集合中)来说,一直是不断增大的,
能够dist[j]>dist[t]+g[t][j] 或 dist[j]==dist[t]+g[t][j],这个j一定是未确定最小距离的点
————————————————
版权声明:本文为CSDN博主「Cyber_Wz」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_50564748/article/details/123143604
《其中的一些小问题》
在上面有特殊条件的dijkstra如果不用heap的方式写,那么就不能够用首先for (int i=1;i<=n;i++) dist[i]=g[1][i]这样的写法了
因为如果这样,那么就在源点1处的特殊状态就不能得到更新
所以这个时候就只能用最简朴的,如这样的方法了:
1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 #include <queue>
5 #include <vector>
6 using namespace std;
7 typedef pair<int, int> PII;
8 const int N = 1e5 + 1, mod = 100003;
9 int n, m;
10 int dist[N];
11 bool st[N];
12 int cnt[N];
13 priority_queue<PII, vector<PII>, greater<PII>> heap;
14 vector<PII> g[N];
15 void dijkstra(int x)
16 {
17 memset(dist, 0x3f, sizeof(dist));
18 dist[x] = 0;
19 cnt[x] = 1;
20 heap.push({0, x});
21
22 while (heap.size())
23 {
24 auto t = heap.top();
25 heap.pop();
26 int v = t.second;
27 /* cout<<"^^^"<<v<<endl; */
28 if (st[v])
29 continue;
30 st[v] = true;
31 /* cout << "fa: " << v << " " << endl; */
32 for (int i = 0; i < g[v].size(); i++)
33 {
34 int child = g[v][i].second, w = g[v][i].first;
35 /* cout << "Nchild: " << child << " "; */
36 if (!st[child])
37 {
38 /* cout << "child: " << child << endl;
39 cout << dist[child] << " " << dist[v] << " " << w << endl; */
40 if (dist[child] > dist[v] + w)
41 {
42 dist[child] = dist[v] + w;
43 cnt[child] = cnt[v];
44 heap.push({dist[child], child});
45 }
46 else if (dist[child] == dist[v] + w)
47 {
48 cnt[child] = (cnt[child] + cnt[v]) % mod;
49 }
50 }
51 }
52 }
53 }
54 int main()
55 {
56 scanf("%d%d", &n, &m);
57 for (int i = 1; i <= m; i++)
58 {
59 int a, b;
60 scanf("%d%d", &a, &b);
61 g[a].push_back({1, b}), g[b].push_back({1, a});
62 }
63 dijkstra(1);
64 for (int i = 1; i <= n; i++)
65 cout << cnt[i] << endl;
66 return 0;
67 }
《状态转移在最短路中的应用(bfs,双端队列的使用其实是特殊的dijkstra)》
我们发现如果没有这些门与钥匙的话,就是个普通的bfs或dijkstra
但是这里有,如果按照普通的bfs我们完全不知道当到达门时我们是否拿到了钥匙
则我们要多存储一项数据
dist[x][y][state]:表示从(1,1)到(x,y)时,有的钥匙状态为state的最短路径
state是以二进制保存钥匙状态,比如:有钥匙类型1,则state二进制状态下为000000001(因为钥匙类型最多有10种,最多10位)
状态转移:
则可以知道这个状态的转移的权重只有0,1,可以用双端队列写
具体题目:https://www.acwing.com/problem/content/1133/
《在最短路中的次短路问题》
这里同样如果只按照最原始的dijkstra算法是数据不够的
所以我们可以在开一维记录次短路的路径
dist[i][0]:表示从初始点到i点的最短路
dist[i][1]:表示从初始点到i点的次短路
cnt[i][0]:表示从初始点到i点的最短路的条数
cnt[i][1]:表示从初始点到i点的次短路的条数
在做dijkstra时可以进一个点看做两个点
因为一个点有两个状态--从最短路更新过来,从次短路更新过来
最短路只能根据最短路更新过来,而次短路可以根据最短路和次短路更新过来
记住要将次短路加入优先队列,因为次短路更新要依据最短路和次短路更新过来
同时次短路也要确认为这是最短的次短路了,则st[N][2]应该是这样的
st[i][1]=true,表示从初始点到i点的次短路已经确定
1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 #include <queue>
5 #include <vector>
6 using namespace std;
7 const int N = 1001, INF = 0x3f3f3f3f;
8 typedef pair<int, int> PII;
9 int t, n, m, s, f;
10 int dist[N][2], cnt[N][2];
11 int g[N][N];
12 bool st[N][2];
13 struct Node
14 {
15 int v, type, w;
16 bool operator>(const Node &node) const
17 {
18 return w > node.w;
19 }
20 };
21 void dijkstra(int start, int final, vector<PII> g[])
22 {
23 memset(dist, 0x3f, sizeof(dist));
24 memset(cnt, 0, sizeof(cnt));
25 memset(st, false, sizeof(st));
26 priority_queue<Node, vector<Node>, greater<Node>> heap;
27 heap.push({start, 0, 0});
28 dist[start][0] = 0, cnt[start][0] = 1;
29 while (heap.size())
30 {
31 auto t = heap.top();
32 heap.pop();
33 int v = t.v, type = t.type, distance = t.w;
34 /* cout<<"~~~"<<v<<" "<<type<<" "<<distance<<endl; */
35 if (st[v][type])
36 continue;
37 st[v][type] = true;
38 for (int i = 0; i < g[v].size(); i++)
39 {
40 int w = g[v][i].second, j = g[v][i].first;
41 /*cout << v << " " << j << " " << w << endl;*/
42 /* if (j == 5)
43 {
44 cout << "!!" << v << " " << type << endl;
45 cout << "!!" << dist[j][0] << " " << dist[v][type] + w << endl;
46 cout << "!!" << dist[j][1] << " " << dist[v][type] + w << endl;
47 } */
48 if (dist[j][0] > distance + w)
49 {
50 dist[j][1] = dist[j][0];
51 cnt[j][1] = cnt[j][0];
52 /* cout << j << " " << 1 << " " << cnt[j][1] << endl; */
53 /* cout << "@@" << dist[j][1] << endl; */
54 heap.push({j, 1, dist[j][1]});
55 dist[j][0] = distance + w;
56 cnt[j][0] = cnt[v][type];
57 /* cout << j << " " << 0 << " " << cnt[j][0] << endl;*/
58 heap.push({j, 0, dist[j][0]});
59 /* cout << "@@" << dist[j][0] << endl; */
60 }
61 else if (dist[j][0] == dist[v][type] + w)
62 cnt[j][0] += cnt[v][type];
63 else if (dist[j][1] > distance + w)
64 {
65 dist[j][1] = distance + w;
66 cnt[j][1] = cnt[v][type];
67 /* cout << j << " " << 1 << " " << cnt[j][1] << endl; */
68 heap.push({j, 1, dist[j][1]});
69 /* cout << "@@" << dist[j][1] << endl; */
70 }
71 else if (dist[j][1] == dist[v][type] + w)
72 cnt[j][1] += cnt[v][type];
73 }
74 }
75 }
76 int main()
77 {
78 cin >> t;
79 while (t--)
80 {
81 cin >> n >> m;
82 vector<PII> g[N];
83 for (int i = 1; i <= m; i++)
84 {
85 int a, b, w;
86 cin >> a >> b >> w;
87 g[a].push_back({b, w});
88 }
89 cin >> s >> f;
90 dijkstra(s, f, g);
91 int res = cnt[f][0];
92 if (dist[f][1] == dist[f][0] + 1)
93 res += cnt[f][1];
94 cout << res << endl;
95 }
96 return 0;
97 }
注意一下在结构体中使用优先队列的方法
《关于优先队列的写法与普通写法的区别》
这是我在使用普通数组的写法,注意观察内存已经爆掉了:
这是我用vector的写法:
可以明显有好转
《floyd算法》
如果在考察两点之间的最短路时,能写floyd算法就写floyd算法,尽量不要去循环1~n,然后在循环中跑Dijkstra算法
因为这样表面上是O(n^3)/O(n^2logn)的算法,但是这样做常数特别大,很可能会超时
但是同样是O(n^3)的floyd算法常数特别小
《floyd原理》
参考博客:理论性:https://oi-wiki.org/graph/shortest-path/#floyd,实践性,理解性:2
从动态规划的角度思考下面的内容会无比轻松:
模拟:
首先从这张无向图入手,假设用floyd算法求其最小路:
我们建立初始的,只能通过相邻点之间直接连接的距离 ,而这个距离也名副其实是最短的
然后下面我们模拟的是floyd算法三重for循环中的第一重循环:
从点1到点n,一个一个点进行“唤醒”,(假设这里的枚举的点为k)
在这个第一重循环下面的两重循环 是枚举图上的任意两个点(假设分别为i和j),
看一下是否能够通过上面枚举的点k进行更新 i和j点之间最短距离
重复上述过程...............
于是我们可以得到如下简单的代码:
众所周知,dp是有可能进行消维的
如何求最短路的路径?
看这里<-----
有向边图咋办?
在初始化图dis[][]时,将没有边的两点设为INF,有边的设为其边权,然后像求无向图一样即可
有决定最短路的有两个条件咋办?
比如每个点有点权,在满足经过点最少的情况下,还有满足全部经过的点,点权之和最大
设置d[][]点数量数组,d[i][j]表示从点i到点j的最短路中进过最少的点数为d[i][j]
设置v[][]点权之和数组,在状态转移时,如果点数相等那么最大化点权之和
注意这里点权之和v[][]有点考究:
v[i][j]代表:点i到点j的最短路径上,点权之和但不包括点j的点权最大
因为在状态转移的时候v[i][k]+v[k][j]
v[i][j]如果设置成i,j之间全部点权之和,那么k点的点权就被加了两次
有向边图初始化,为两点之间有边,点为自己,无边的情况:
本文作者:次林梦叶的小屋
本文链接:https://www.cnblogs.com/cilinmengye/p/16410057.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步