学习思路(yxc总结):

img点击并拖拽以移动


一. 朴素Dijkstra算法 (稠密图)****<邻接矩阵>

算法思路:

img点击并拖拽以移动 更新过程:

img点击并拖拽以移动

acwing849. Dijkstra求最短路 I

img点击并拖拽以移动

此题思路(分解版):

1.读入的同时需要更新边权

1
g[a][b] = min(g[a][b], c);//g[][]存放的为最小边权

点击并拖拽以移动

2.Dijkstra算法 (核心步骤),循环思路对应朴素Dijkstra算法的第二步

1
2
3
4
5
6
7
8
9
10
11
12
for (int i = 0; i < n; i ++ )//循环每一个点
{
int t = -1;//初始化
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || d[t] > d[j]))//没有确定最短距离的点 + 没有更新过t/距离更近的点
t = j;

st[t] = true;//标记t这个点已确定最短距离

for (int j = 1; j <= n; j ++ )//用t来更新其他点的距离
d[j] = min(d[j], d[t] + g[t][j]);
}

点击并拖拽以移动

全部代码实现+注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510;
int n, m;
int d[N];//某点到起点的最短距离
int g[N][N];//邻接矩阵存边权
bool st[N];//当前已确定最短距离的点标记为true

int dijkstra()
{
memset(d, 0x3f, sizeof d);//初始化所有点到起点的距离为无穷大
d[1] = 0;//从第一个点开始
for (int i = 0; i < n; i ++ )//循环每一个点
{
int t = -1;//初始化
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || d[t] > d[j]))//没有确定最短距离的点 + 没有更新过t/距离更近的点
t = j;

st[t] = true;//标记t这个点已确定最短距离

for (int j = 1; j <= n; j ++ )//用t来更新其他点的距离
d[j] = min(d[j], d[t] + g[t][j]);
}
if (d[n] == 0x3f3f3f3f) return -1;
return d[n];
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
while (m --)
{
int a, b, c;
scanf("%d%d%d",&a,&b,&c);
g[a][b] = min(g[a][b], c);//g[][]存放的为最小边权
}
int ans = dijkstra();
printf("%d\n",ans);
}

点击并拖拽以移动


二. 堆优化版Dijkstra算法(稀疏图)<邻接表>

算法思路:

利用邻接表,优先队列
在priority_queue[HTML_REMOVED], greater[HTML_REMOVED] > heap;中将返回堆顶
利用堆顶来更新其他点,并加入堆中类似宽搜

更新过程:

img点击并拖拽以移动

img点击并拖拽以移动

img点击并拖拽以移动

acwing 850. Dijkstra求最短路 II

img点击并拖拽以移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e6 + 10;
typedef pair<int, int> PII;

int n, m;
int h[N], e[N], ne[N], w[N], idx;
priority_queue<PII, vector<PII>, greater<PII>> heap;//小根堆
int d[N];//节点到起点的最短距离
bool st[N];

void add(int a, int b, int c)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx ++;
}
int Dijkstra()
{
memset(d, 0x3f, sizeof d);
d[1] = 0;
heap.push({0, 1});//距离 + 节点
while (heap.size())
{
auto t = heap.top();//堆顶取出
heap.pop();
int ver = t.second;//节点编号
int distance = t.first;//记录距离
if (st[ver]) continue;//如果说当前节点已经确定,就跳过以下语句
st[ver] = true;//要进行以下语句就给当前节点打上标记
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];//节点编号
if (d[j] > distance + w[i])
{
d[j] = distance + w[i];//更新节点距离
heap.push({d[j], j});
}
}
}
if (d[n] == 0x3f3f3f3f) return -1;
return d[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
int ans = Dijkstra();
cout << ans << endl;
}

点击并拖拽以移动


三. Bellman_Ford算法(结构体存储边及边权)<结构体>

定义:

连续进行松弛,在每次松弛时把每条边都更新一下,若在 n-1 次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。

假设 1 号点到 n 号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环 n-1 次操作,若图中不存在负环,则 1 号点一定会到达 n 号点,若图中存在负环,则在 n-1 次松弛后一定还会更新

算法思路:

img点击并拖拽以移动

更新过程:

img点击并拖拽以移动

img点击并拖拽以移动

img点击并拖拽以移动

img点击并拖拽以移动

853. 有边数限制的最短路

img点击并拖拽以移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510, M = 10010;

struct Edge
{
int a, b, w;
}edges[M];
int n, m, k;
int d[N], backup[N];

int bellman_ford()
{
memset(d, 0x3f, sizeof d);
d[1] = 0;

for (int i = 0; i < k; i ++ )
{
memcpy(backup, d, sizeof d);
for (int j = 0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
d[b] = min(d[b], backup[a] + w);
}
}
return d[n];
}
int main()
{
cin >> n >> m >> k;
for (int i = 0; i < m; i ++ )
{
int a, b, w;
cin >> a >> b >> w;
edges[i] = {a, b, w};
}
int ans = bellman_ford();
if (ans >= 0x3f3f3f3f / 2) puts("impossible");
else printf("%d\n",ans);
}

点击并拖拽以移动


四. Spfa算法<邻接表>

算法思路:

img点击并拖拽以移动

更新过程:

img点击并拖拽以移动 img点击并拖拽以移动 img点击并拖拽以移动

851. spfa求最短路

img点击并拖拽以移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//spfa
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], w[N], idx;//邻接表
int d[N];
bool st[N];//用于判断队列中是否有重复的点

void add(int a, int b, int c)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx ++;
}
int spfa()
{
memset(d, 0x3f, sizeof d);
d[1] = 0;
st[1] = true;//在队列之中
queue<int> q;
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (d[j] > d[t] + w[i])
{
d[j] = d[t] + w[i];

if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return d[n];
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
int ans = spfa();
if (ans >= 0x3f3f3f3f) printf("impossible\n");
else printf("%d\n", ans);
}

点击并拖拽以移动

852. spfa判断负环

img点击并拖拽以移动

注意:

判断负环时,起点不止一个,所以要将所有的点都插入队列当中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;
const int N = 10010;
int n, m;
int h[N], e[N], ne[N], w[N], idx;
int d[N], cnt[N];//cnt用来存储边数
bool st[N];


void add(int a, int b, int c)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx ++;
}
bool spfa()
{
queue<int> q;
for (int i = 1 ; i <= n; i ++ )
{
st[i] = true;
q.push(i);
}
while (q.size())
{
int t = q.front();
q.pop();

st[t] = false;

for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (d[j] > d[t] + w[i])
{
d[j] = d[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (!st[j])
{
st[j] = true;
q.push(j);
}
}

}
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d",&n,&m);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d",&a,&b,&c);
add(a, b, c);
}
if (spfa()) puts("Yes");
else puts("No");
}

点击并拖拽以移动

四. Floyd算法(多源汇最短路,多个起点)<邻接矩阵>

闫氏Dp分析法:

img点击并拖拽以移动

状态转移方程:

img点击并拖拽以移动

算法思路:

img点击并拖拽以移动

854. Floyd求最短路

img点击并拖拽以移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 210, INF = 1e9;
int m, n, Q;
int d[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 ++ )
{
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&Q);
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
}
}
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d",&a,&b,&c);
d[a][b] = min(d[a][b], c);
}
Floyd();
while (Q --)
{
int a, b;
scanf("%d%d",&a,&b);
if (d[a][b] >= INF / 2) printf("impossible\n");
else printf("%d\n",d[a][b]);
}

return 0;
}

点击并拖拽以移动

pintia 7-17 城市间紧急救援

img点击并拖拽以移动

此题思路:

大体上是朴素dijkstra算法

参考了别人的代码,自己小改了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 1e9;
int n, m, s, d;
//城市个数,m个操作,起点,终点
int g[N][N];//存储边权
int cnt[N];//最短路径的条数
int save[N];//各个城市的救援队数量
int sum_save[N];//到达该城市的所有救援队
int e[N];//到达目标城市前经过的城市编号
int path[N], q;//路径
bool st[N];//判断城市是否经过

void dijkstra()//朴素dijkstra算法
{
cnt[s] = 1, st[s] = true;//标记起点经过
for (int i = 0; i < n; i ++ )
{
int min = INF;
int t = -1;

for (int j = 0; j < n; j ++ )
if (!st[j] && g[s][j] < min) min = g[s][j], t = j;

if (t == -1) break;//找不到连通的点
st[t] = true;//找到标记该点经过

for (int j = 0; j < n; j ++ )
{
if (!st[j] && g[s][j] > g[s][t] + g[t][j])//找到一个最短路径
{
g[s][j] = g[s][t] + g[t][j];
e[j] = t;//t为经过的点
cnt[j] = cnt[t];
sum_save[j] = sum_save[t] + save[j];
}
else if(!st[j] && g[s][j] == g[s][t] + g[t][j])//找到相同的最短路径
{
cnt[j] = cnt[j] + cnt[t];//更新最短路径条数
if (sum_save[j] < sum_save[t] + save[j])
{
sum_save[j] = sum_save[t] + save[j];//更新最大救援队数量
e[j] = t;
}
}
}

}
}
int main()
{
cin >> n >> m >> s >> d;
for (int i = 0; i < n; i ++ )
scanf("%d",&save[i]), sum_save[i] = save[i], cnt[i] = 1;//读入各个城市的救援队数量并且对sum_save进行初始化

//初始化
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < n; j ++ )
{
if (j == i) g[i][j] = 0;
else g[i][j] = INF;
}
}
for (int i = 0; i < m; i ++ )//m个操作
{
int a, b, w;
scanf("%d%d%d",&a,&b,&w);//无向图
g[a][b] = w;
g[b][a] = w;
}
dijkstra();
printf("%d %d\n",cnt[d], sum_save[d] + save[s]);
for (int i = e[d]; i != 0; i = e[i]) path[q ++] = i;

printf("%d ", s);
for (int i = q - 1; i >= 0; i -- ) printf("%d ",path[i]);
printf("%d\n", d);
}


点击并拖拽以移动