总览
单源最短路的一些算法
{ 边 权 非 负 { 朴 素 D i j k s t r a 堆 优 化 D i j k s t r a 有 负 权 边 { B e l l m a n − F o r d ( 用 的 情 况 少 ) s p f a ( 99 % 情 况 用 这 种 算 法 ) \left\{ \begin{matrix}边权非负\left\{\begin{matrix} 朴素Dijkstra \\ 堆优化Dijkstra \end{matrix}\right.\\ 有负权边\left\{\begin{matrix} Bellman-Ford (用的情况少)\\ spfa (99\%情况用这种算法) \end{matrix}\right.\\ \end{matrix} \right. ⎩⎪⎪⎨⎪⎪⎧边权非负{
朴素Dijkstra堆优化Dijkstra有负权边{
Bellman−Ford(用的情况少)spfa(99%情况用这种算法)
图论问题,最重要的在于问题的转化与抽象
AcWing 1129. 热浪
分析
无向图,求起点到终点的最短路径(裸题)
最简单的单源最短路模型
朴素版dijkstra
时间复杂度 O ( n 2 ) O(n^2) O(n2) 250 0 2 2500^2 25002可以过
堆优化版dijkstra
时间复杂度 O ( m l o g n ) O(mlogn) O(mlogn), 6200 ∗ l o g 2500 6200 * log {2500} 6200∗log2500 可以过
spfa
平均时间复杂度 O ( m ) O(m) O(m), 也可以过
随便选择一个。
其中,
spfa
可以采用循环队列, 来节省队列长度
while (hh != tt){
if (hh == N) hh = 0;
...
if (tt == N) tt = 0;
}
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2510, M = 6210 * 2;
int n, m, S, E;
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 ++;
}
int spfa(){
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
int hh = 0, tt = 1; // tt表示当前位置, 还没放数字, 因此后面是tt ++, 而不是 ++ tt
q[0] = S, st[S] = true;
while (hh != tt){
int t = q[hh ++];
if (hh == N) hh = 0;
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[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return dist[E];
}
int main(){
cin >> n >> m >> S >> E;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
int t = spfa();
cout << t << endl;
return 0;
}
AcWing 1128. 信使
分析
题目问整个图中, 所有点被广播到需要多少时间,转化一下,就是求最短距离中最长的距离.
这有点意思啊…
dijkstra代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 110, M = 210 * 2, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
int n, m;
int dist[N];
bool st[N];
int g[N][N];
int dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({
0, 1});
while (heap.size()){
auto t = heap.top(); heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int j = 1; j <= n; j ++ ){
if (dist[j] > dist[ver] + g[ver][j]){
dist[j] = dist[ver] + g[ver][j];
heap.push({
dist[j], j});
}
}
}
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i != j) g[i][j] = g[j][i] = INF;
else g[i][j] = g[j][i] = 0;
for (int i = 0; i < m; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = dijkstra();
int res = 0;
for (int i = 2; i <= n; i ++ ) res = max(res, dist[i]);
if (res == 0x3f3f3f3f) cout << -1 << endl;
else cout << res << endl;
return 0;
}
floyd 代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 110, M = 210 * 2, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
int n, m;
int dist[N][N];
bool st[N];
int g[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 ++ )
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i != j) dist[i][j] = dist[j][i] = INF;
else dist[i][j] = dist[j][i] = 0;
for (int i = 0; i < m; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
dist[a][b] = dist[b][a] = min(dist[a][b], c);
}
floyd();
int res = 0;
for (int i = 2; i <= n; i ++ ) res = max(res, dist[1][i]);
if (res == 0x3f3f3f3f) cout << -1 << endl;
else cout << res << endl;
return 0;
}
AcWing 1127. 香甜的黄油
分析
就是求图中一个点 到 给出的所有奶牛位置的最短距离之和的最小值
在求最短路的时候, 外层还要套一层n
次起点.
floyed(TLE)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 810, M = 1460 * 2, INF = 0x3f3f3f3f;
int d[N][N];
int n, P, m;
int a[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] = d[j][i] = min(d[i][j], d[i][k] + d[k][j]);
}
int main(){
cin >> P >> n >> m;
for (int i = 0; i < P; i ++ ){
cin >> a[i];
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i != j) d[i][j] = d[j][i] = INF;
else d[i][i] = 0;
for (int i = 1; i <= m; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = d[b][a] = min(d[a][b], c);
}
// cout << d[2][4] << d[3][4] << endl;
floyd();
int res = INF;
for (int i = 1; i <= n; i ++ ) {
int t = 0;
for (int j = 0; j < P; j ++ )
t += d[a[j]][i];
res = min(t, res);
}
cout << res << endl;
return 0;
}
spfa
#include <iostream>
#include <cstring>
using namespace std;
const int N = 810, M = 1460 * 2, INF = 0x3f3f3f3f;
int h[N], e[M], w[M], ne[M], idx;
bool st[N];
int q[N];
int n, p, m;
int id[N];
int dist[N];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int spfa(int start){
memset(dist, 0x3f, sizeof dist);
dist[start] = 0;
int hh = 0, tt = 1;
q[0] = start, st[start] = true;
while (hh != tt){
int t = q[hh ++];
if (hh == N) hh = 0;
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[tt ++] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
int res = 0; // 计算所有奶牛到起点的距离
for (int i = 0; i < n; i ++ ){
int j = id[i];
int t = dist[j];
if (t == INF) return INF;
else res += t;
}
return res;
}
int main(){
cin >> n >> p >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < n; i ++ ) cin >> id[i];
for (int i = 0; i < m; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
int res = INF;
for (int i = 1; i <= p; i ++ ) res = min(res, spfa(i)); // 计算所有起点开始到奶牛的最小距离
cout << res << endl;
return 0;
}
堆优化dijkstra
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 810, M = 1460 * 2, INF = 0x3f3f3f3f;
int h[N], e[M], w[M], ne[M], idx;
bool st[N];
int q[N];
int n, p, m;
int id[N];
int dist[N];
typedef pair<int, int> PII;
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(st, 0, sizeof st); // st数组每次需要重制. 别忘了
memset(dist, 0x3f, sizeof dist);
dist[start] = 0;
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, distance = t.first;
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 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(){
cin >> n >> p >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < n; i ++ ) cin >> id[i];
for (int i = 0; i < m; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
int res = INF;
for (int i = 1; i <= p; i ++ ) res = min(res, dijkstra(i)); // 计算所有起点开始到奶牛的最小距离
cout << res << endl;
return 0;
}
AcWing 1126. 最小花费
分析
A 最少需要多少钱使得转账后 B 收到 100 元
100 = d(A) * W1 * W2 * … Wn
要让 A的值最小 则(W1 * W2 * … Wn)
最大
证明最短路模型对于 乘法也成立
100 = d(A) * W1 * W2 * … Wn
对W1 * W2 * … Wn 取log, 那么每条边权重会变成 l o g ( w i ) log(w_i) log(wi),题目转化为求A到B的和最大的路径(因为 W i W_i Wi乘法变成加法了, 可以用最短路模型了) , 对每条边* (-1), 使得权重变成正的, 就会转化成权重为 − l o g ( w i ) > 0 -log(w_i) > 0 −log(wi)>0的最短路问题.
0 < w i ≤ 1 0 < w_i \leq 1 0<wi≤1, l o g ( w i ) < 0 log(w_i) < 0 log(wi)<0.
注意
w i ≤ 1 w_i \leq 1 wi≤1, 使得取完log * (-1), 权重都为正的, 所以才可以dijkstra. 如果没有这个条件, 只能spfa
spfa代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2010, M = 100010 * 2;
int h[N], e[M], w[M], ne[M], idx;
int n, m;
int S, T;
double dist[N];
int 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(){
dist[T] = 1;
int hh = 0, tt = 1;
q[0] = T, st[T] = true;
while (hh != tt){
int t = q[hh ++];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]){
int j = e[i];
if (dist[j] < (double)dist[t] * (double)(100 - w[i]) / 100.0){
dist[j] = (double)dist[t] * (double)(100 - w[i]) / 100.0;
if (!st[j]){
q[tt ++] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
}
int main(){
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
cin >> S >> T;
spfa();
printf("%.8lf\n", 100.0 / dist[S]);
return 0;
}
朴素dijkstra代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2010, M = 100010 * 2;
int h[N], e[M], w[M], ne[M], idx;
int n, m;
int S, T;
double dist[N];
bool st[N];
double g[N][N];
void dijkstra(){
// 因为求最大, 所以dist = 0, 表示此路不通
dist[S] = 1;
for (int i = 0; i < n; i ++ ){
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] < dist[j])) t = j; // 找出s中距离原点最大的
st[t] = true;
for (int j = 1; j <= n; j ++)
dist[j] = max(dist[j], dist[t] * g[t][j]);
}
}
int main(){
cin >> n >> m;
// g = 0表示路不通
for (int i = 0; i < m; i ++ ){
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = max(g[a][b], (100 - c) / 100.0);
}
cin >> S >> T;
dijkstra();
printf("%.8lf\n", 100.0 / dist[T]);
return 0;
}
总结
对于乘法的最短路问题.
如果权重 > 1, 那么可以dijkstra
如果权重 >= 0, 那么只能spfa
AcWing 920. 最优乘车
分析
可以先求坐车的最小次数, 然后 - 1, 就是换车的最小次数
然后再每段车的起点,往后面的终点连一条边权重为1的边, 然后求1-n的最短路径, 再-1,就是换车次数了
特判下 1 == n, 会减成-1, 输出0.
边权为1的话,用bfs
就可以了
代码
#include <iostream>
#include <cstring>
#include <sstream>
using namespace std;
const int N = 510;
int dist[N];
int g[N][N];
int n, m;
int q[N];
int stop[N];
void bfs(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
int hh = 0, tt = 0;
q[0] = 1;
while (hh <= tt){
int t = q[hh ++];
// bfs不用判重, 每次出队就是最小值
for (int i = 1; i <= n; i ++ )
if (g[t][i] && dist[i] > dist[t] + 1){
dist[i] = dist[t] + 1;
q[++ tt] = i;
}
}
}
int main(){
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) puts("NO");
else cout << max(0, dist[n] - 1) << endl;
return 0;
}
AcWing 903. 昂贵的聘礼
分析
读题, 发现一个物品, 有多条路径, 其中一种方式是某个点直接可以和这个点相连, 在其他物品中也发现都存在这个点, 可以将这个点建立为虚拟源点
然后对于等级限制, 暴力枚举从最低范围内的等级为起点, 最高范围为酋长, 求最短路
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int w[N][N], dist[N];
bool st[N];
int n, m;
int level[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] = true;
for (int j = 1; j <= n; j ++ )
if (level[j] >= down && level[j] <= up)
dist[j] = min(dist[j], dist[t] + w[t][j]);
}
return dist[1];
}
int main(){
cin >> m >> n;
memset(w, 0x3f, sizeof w);
for (int i = 1; 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(w[0][i], price);
while (cnt --){
int id, cost;
cin >> id >> cost;
w[id][i] = min(w[id][i], cost);
}
}
int res = INF;
for (int i = level[1] - m; i <= level[1]; i ++ ) res = min(res, dijkstra(i, i + m));
cout << res << endl;
return 0;
}