问题
解决这样一类问题,起点不确定的,终点也不定的,可以有重边和自环的,各边权可能为负数的图的最短路问题
注意,图中一定不能有负权回路
要素
g[][]
邻接矩阵,存储图的信息,同时在程序结束后存储的就是各点间的最短距离
思路
本质上是基于动态规划,利用三角不等式 g[a][b] = Math.min(g[a][b], g[a][k] + g[k][b]
三重for循环搞定
时间复杂度:O(n^3)
状态表示:f[k][i][j]
表示从i
出发,最终走到j
,且中间只经过节点编号不超过k
的所有路径(类似于背包问题)路径长度的最小值
状态计算:
经过第k
个节点f[k][i][j] = Math.min(f[k][i][j], f[k - 1][i][k] + f[k - 1][k][j])
不经过第k
个节点f[k][i][j] = Math.min(f[k][i][j], f[k - 1][i][j])
去掉k
所在维,结果不变,得到Floyed做法
模板
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。
再给定 k个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出 impossible
。
数据保证图中不存在负权回路。
输入格式
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
接下来 k 行,每行包含两个整数 x,y,表示询问点 x 到点 y的最短距离。
输出格式
共 k行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible
。
数据范围
1≤n≤200,
1≤k≤n2
1≤m≤20000,
图中涉及边长绝对值均不超过 10000。
输入样例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
输出样例:
impossible
1
import java.util.*;
public class Main {
static int n, m;
static int[][] g;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
int k = sc.nextInt();
g = new int[n + 1][n + 1];
// 初始化
for (int i = 1; i<= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) g[i][j] = 0;
else g[i][j] = 0x3f3f3f3f;
}
}
while (m-- > 0) {
int a, b, c;
a = sc.nextInt();
b = sc.nextInt();
c = sc.nextInt();
g[a][b] = Math.min(g[a][b], c);
}
floyd();
while (k-- > 0) {
int x = sc.nextInt();
int y = sc.nextInt();
if (g[x][y] > 0x3f3f3f3f / 2) System.out.println("impossible");
else System.out.println(g[x][y]);
}
}
static void floyd() {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]);
}
}
}
}
}
题型
最短路
AcWing 1125. 牛的旅行
思路:
a. floyed求出任意两点之间的最短距离
b. 求maxd[i]
表示和i
连通的且距离i
最远的点的距离
c. 枚举在哪两个边之间连边,i, j 且 dist[i][j] == INF
maxd[i] + maxd[j] + d[i][j]
d.res = Max(maxd[i], min(maxd[i] + maxd[j] + d[i][j]))
传递闭包
floyed-warshall算法 :::info 传递闭包:a->b->c,则为a, c之间连一条边:a -> c ::: AcWing 343. 排序
- 找最小环
spfa是找负环,Floyed是找最小的环(对于正权图来讲)
AcWing 344. 观光之旅
思路:
将所有环分类,依据环上编号最大的节点编号确定一个环
恰好就是floyed算法枚举的k
,而i, j
就是枚举环上直接和k
相连的两条边,并且能保证枚举k
时,dist[i, j]
为只经过前k - 1
个节点的最短路径
本题还有一个要求是输出最小环的所有节点,在更新最小环时,顺便记录该环的所有节点即可。
如何记录?利用图论的性质g[a][b] >= g[a][k] + g[k][b
,递归找使得g[a][b] == g[a][k] + g[k][b]
的点k
即可。
- 恰好经过
k
条边的最短路
AcWing 345. 牛站
换一种DP的状态表示f[k][i][j]
从i
到j
恰好经过k
条边的最短路径
结合快速幂
时间复杂度:O(N3logM)
// 注意初始化 g[i][i]不能初始化为0
import java.util.*;
public class Main {
static final int N = 210, INF = 0x3f3f3f3f;
static int[][] g = new int[N][N], f = new int[N][N];
static int n, K, S, E, m;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
K = sc.nextInt();
m = sc.nextInt();
S = sc.nextInt();
E = sc.nextInt();
for (int i = 1; i < N; i++)
for (int j = 1; j< N; j++)
g[i][j] = INF;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < m; i++) {
int w = sc.nextInt();
int a = sc.nextInt(), b = sc.nextInt();
if (!map.containsKey(a))
map.put(a, ++n);
if (!map.containsKey(b))
map.put(b, ++n);
a = map.get(a);
b = map.get(b);
g[a][b] = g[b][a] = Math.min(g[a][b], w);
}
qmi();
System.out.println(f[map.get(S)][map.get(E)]);
}
static void qmi() {
for (int i = 0; i < N; i++) {
Arrays.fill(f[i], INF);
f[i][i] = 0;
}
while (K > 0) {
if ((K & 1) == 1)
mul(f, f, g);
K >>= 1;
mul(g, g, g);
}
}
static void mul(int[][] c, int[][] a, int[][] b) {
int[][] back = new int[n + 1][n + 1];
for (int i = 1; i <= n; i++)
Arrays.fill(back[i], INF);
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
back[i][j] = Math.min(back[i][j], a[i][k] + b[k][j]);
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
c[i][j] = back[i][j];
}
}
}