武汉到北京的
给定带权有向图$\Gcal = (\Vcal, \Ecal, w)$
路径$p = \langle v_0, v_1, \ldots, v_k \rangle$的长度$l(p) = \sum_{i=1}^k w(v_{i-1}, v_i)$是路径上所有边的权重之和
问题目标:寻找源点$s$到目的点$t$的最短路径:
$$ \begin{align*} \quad \delta(s,t) = \begin{cases} \min_p \{ l(p) : s \overset{p}{\rightsquigarrow} t \}, & \text{如果存在 } s \text { 到 } t \text{ 的路径} \\ \infty, & \text{其它} \end{cases} \end{align*} $$
权重函数$w: \Ecal \mapsto \Rbb$的值域为$\Rbb$表示边上权重可以为负,对应于走该边可以挣一些钱
几乎所有求图上两点间最短路径的问题都会归结为更一般的
单源最短路径问题 (single source shortest path, SSSP)
目标:求源点$s$到其它所有点的最短路径
输出:以源点$s$为根结点的最短路径树 (shortest path tree)
最短路径是简单路径,即不包含环路、最多有$|\Vcal|-1$条边
path:每个点最多访问一次,否则称之为 walk
对任意点$v$
初始化:$d_s = 0$、$d_{v \ne s} = \infty$、$p_v = \text{NULL}$
松弛 (relax):若$d_v < d_u + w(u,v)$,令$d_v = d_u + w(u,v)$、$p_v = u$
Ford 算法框架:初始化所有点,若还有边可以松弛,松弛该边
若$\Gcal$是无权图,路径长度即为经过的边的数目
Ford 算法框架实现:
上述实现即为以$s$为起点的广度优先遍历
松弛
$$ \begin{align*} \quad d_v = \begin{cases} 0, & v = s \\ \min_u \{ d_v + w(u,v) \}, & \text{其它} \end{cases} \end{align*} $$
Ford 算法框架实现:
必须是无环图,否则会出现死循环
设路径$p = \langle v_0, v_1, \ldots, v_k \rangle$是从$v_0$到$v_k$的一条最短路径,则其任意子路径$\langle v_i, v_{i+1}, \ldots, v_j \rangle$是从$v_i$到$v_j$的最短路径
此时路径$p$分为三部分,$v_0 \overset{p_1}{\rightsquigarrow} v_i \overset{p_2}{\rightsquigarrow} v_j \overset{p_3}{\rightsquigarrow} v_k$
若$p_2$不是最短路径,设$p'_2$更短,则$v_0 \overset{p_1}{\rightsquigarrow} v_i \overset{p'_2}{\rightsquigarrow} v_j \overset{p_3}{\rightsquigarrow} v_k$也更短
源点到不同点的最短路径显然存在公共的子路径
最优化问题 + 最优子结构 + 公共子问题 ?
如何定义子问题并确定其递推关系?若采用自底向上,何为底?
以有向边确定子问题间的依赖?环会导致死循环
最短路径的边数有上界,不超过$|\Vcal|-1$
$$ \begin{align*} \qquad \qquad \begin{matrix} \text{经过不超过 }|\Vcal|-1\text{ 条边的最短路径的长度} \\ \vdots \\ \text{经过不超过 }2\text{ 条边的最短路径的长度} \\ \text{经过不超过 }1\text{ 条边的最短路径的长度} \\ \end{matrix} \qquad \quad \Bigg \Uparrow \end{align*} $$
$d_v^{(k)}$:从$s$到$v$经过不超过$k$条边的最短路径的长度
直觉上允许经过的边越多,最短路径越短
当允许经过的边数从$k-1$条放宽至$k$条
$$ \begin{align*} \quad d_v^{(k)} = \begin{cases} \infty, & k = 0 \\ \min \{ d_v^{(k-1)}, ~ \min_{u \ne v} \{ d_u^{(k-1)} + w(u,v) \} \}, & k \ge 1 \end{cases} \end{align*} $$
$$ \begin{align*} \quad d_v^{(k)} = \begin{cases} \infty, & k = 0 \\ \min \{ d_v^{(k-1)}, ~ \min_{u \ne v} \{ d_u^{(k-1)} + w(u,v) \} \}, & k \ge 1 \end{cases} \end{align*} $$
最终$d_u^{(|\Vcal|-1)} = \delta(s,v)$就是源点$s$到$v$的最短路径的长度
def bellman_ford(s): d, p = dict(), dict() for v in g: # 距离初始化为无穷大 前驱初始化为空 d[v], p[v] = float("inf"), None d[s] = 0 # 源点到自己的最短路径长度为零 for _ in range(len(g) - 1): # 遍历|V|-1次 for u in g: for v in g[u]: # 内部的二重for循环遍历所有边 if d[v] > d[u] + g[u][v]: # 松弛 d[v], p[v] = d[u] + g[u][v], u # 更新当前最短距离和前驱
内层二重 for 循环其实是遍历所有边,时间复杂度$\Theta(|\Vcal||\Ecal|)$
$\quad \small |\Vcal|=5, ~ d_v^{(k)} = \begin{cases} \infty, & k = 0 \\ \min \{ d_v^{(k-1)}, ~ \min_{u \ne v} \{ d_u^{(k-1)} + w(u,v) \} \}, & k \ge 1 \end{cases}$
对任意边$(u,v)$应有三角不等式$\delta(s,v) \le \delta(s,u) + w(u,v)$
假如图中有从源点可达的负环$v_i, v_{i+1}, \ldots, v_{j-1}, v_j$,其中$v_j = v_i$
$$ \begin{align*} \quad 0 & = \sum_{k=i}^{j-1} \delta(s,v_k) - \sum_{k=i}^{j-1} \delta(s,v_{k+1}) \overset{\text{三角不等式}}{\le} ~ \sum_{k=i}^{j-1} w(v_k,v_{k+1}) \overset{\text{负环}}{<} 0 \end{align*} $$
故图中若有负环,三角不等式不可能成立
负环检测:在求完所有最短路径后,对所有边检测一遍三角不等式,时间复杂度$\Theta(|\Ecal|)$
def bellman_ford(g, s): d, p = dict(), dict() for v in g: # 距离初始化为无穷大 前驱初始化为空 d[v], p[v] = float("inf"), None d[s] = 0 # 源点到自己的最短路径长度为零 print(0, d) for i in range(len(g) - 1): # 遍历|V|-1次 dd = {key: value for (key, value) in d.items()} # 备份上一轮的d for u in g: for v in g[u]: # 内部的二重for循环遍历所有边 if d[v] > dd[u] + g[u][v]: # 松弛 d[v], p[v] = dd[u] + g[u][v], u # 更新当前最短距离和前驱 print(i+1, d) for u in g: for v in g[u]: assert (d[v] <= d[u] + g[u][v]), "有负环" return d, p g = { # 用集合表示有向图g 元素为字典 "s": {"t": 6, "y": 7}, # w(s,t) = 6, w(s,y) = 7 "t": {"x": 5, "z": -4, "y": 8}, # w(t,x) = 5, w(t,z) = -4, w(t,y) = 8 "y": {"z": 9, "x": -3}, # w(y,z) = 9, w(y,x) = -3 "z": {"x": 7, "s": 2}, # w(z,x) = 7, w(z,s) = 2 "x": {"t": -2}, # w(x,t) = -2 } d, p = bellman_ford(g, s="s") # --------------------------------------------------- # 0 {'s': 0, 't': inf, 'y': inf, 'z': inf, 'x': inf} # 1 {'s': 0, 't': 6, 'y': 7, 'z': inf, 'x': inf} # 2 {'s': 0, 't': 6, 'y': 7, 'z': 2, 'x': 4} # 3 {'s': 0, 't': 2, 'y': 7, 'z': 2, 'x': 4} # 4 {'s': 0, 't': 2, 'y': 7, 'z': -2, 'x': 4}
Q1:外层循环一定要迭代$|\Vcal| - 1$次吗?
A:不是,当相邻两轮的$d[]$表不再有更新时即可停止
Q2:迭代多少轮$d[]$才会不再有更新呢?
A:若所有点的最短路径的边数最大为$k$,则需迭代$k$轮
证明:设$s \rightsquigarrow v$的最短路径上$v$的前驱是$u$,即$s \rightsquigarrow u \rightarrow v$
若某轮$d_u$更新为$\delta (s,u)$,则下一轮$d_v = \delta (s,u) + w(u,v) = \delta (s,v)$
初始$d_s = \delta (s,s) = 0$,第一轮后最短路径恰为 1 条边的点就对了
依此类推,第$i$轮后最短路径恰为$i$条边的点就对了
最短路径最长为$|\Vcal| - 1$,最坏情况下需迭代$|\Vcal| - 1$轮
def bellman_ford(g, s): d, p = dict(), dict() for v in g: # 距离初始化为无穷大 前驱初始化为空 d[v], p[v] = float("inf"), None d[s] = 0 # 源点到自己的最短路径长度为零 print(0, d) for i in range(len(g) - 1): # 遍历|V|-1次 dd = {key: value for (key, value) in d.items()} # 备份上一轮的d for u in g: for v in g[u]: # 内部的二重for循环遍历所有边 if d[v] > dd[u] + g[u][v]: # 松弛 d[v], p[v] = dd[u] + g[u][v], u # 更新当前最短距离和前驱 print(i+1, d) for u in g: for v in g[u]: assert (d[v] <= d[u] + g[u][v]), "有负环" return d, p g = { # s -> x -> y -> z -> t "s": {"x": 1}, # w(s,x) = 1 "x": {"y": 1}, # w(x,y) = 1 "y": {"z": 1}, # w(y,z) = 1 "z": {"t": 1}, # w(z,t) = 1 "t": {} } d, p = bellman_ford(g, s="s") # --------------------------------------------------- # 0 {'s': 0, 'x': inf, 'y': inf, 'z': inf, 't': inf} # 1 {'s': 0, 'x': 1, 'y': inf, 'z': inf, 't': inf} # 2 {'s': 0, 'x': 1, 'y': 2, 'z': inf, 't': inf} # 3 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': inf} # 4 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': 4} g = { # s -> x -> y -> {z, t} "s": {"x": 1}, # w(s,x) = 1 "x": {"y": 1}, # w(x,y) = 1 "y": {"z": 1, "t": 1}, # w(y,z) = 1, w(y,t) = 1 "z": {}, "t": {} } d, p = bellman_ford(g, s="s") # --------------------------------------------------- # 0 {'s': 0, 'x': inf, 'y': inf, 'z': inf, 't': inf} # 1 {'s': 0, 'x': 1, 'y': inf, 'z': inf, 't': inf} # 2 {'s': 0, 'x': 1, 'y': 2, 'z': inf, 't': inf} # 3 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': 3} # 4 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': 3} g = { # s -> {x, y}, y -> {z, t} "s": {"x": 1, "y": 1}, # w(s,x) = 1, w(s,y) = 1 "x": {}, "y": {"z": 1, "t": 1}, # w(y,z) = 1, w(y,t) = 1 "z": {}, "t": {} } d, p = bellman_ford(g, s="s") # --------------------------------------------------- # 0 {'s': 0, 'x': inf, 'y': inf, 'z': inf, 't': inf} # 1 {'s': 0, 'x': 1, 'y': 1, 'z': inf, 't': inf} # 2 {'s': 0, 'x': 1, 'y': 1, 'z': 2, 't': 2} # 3 {'s': 0, 'x': 1, 'y': 1, 'z': 2, 't': 2} # 4 {'s': 0, 'x': 1, 'y': 1, 'z': 2, 't': 2}
《算法导论》上 Bellman-Ford 算法没要求备份上一轮的$d[]$表
$$ \begin{align*} \quad d_v = \begin{cases} \infty, & k = 0 \\ \min \{ d_v, ~ \min_{u \ne v} \{ d_u + w(u,v) \} \}, & k \ge 1 \end{cases} \end{align*} $$
区别:如果本轮$d_u$更新了
任意点的$d$值单调递减,最新的就是坠吼的,用坠吼的没毛病!
但是当采用即时更新时,点的更新顺序会产生影响
def bellman_ford_dp(g, s): d = dict() for v in g: # 距离初始化为无穷大 前驱初始化为空 d[v] = float("inf") d[s] = 0 # 源点到自己的最短路径长度为零 print(0, d) for i in range(len(g) - 1): # 遍历|V|-1次 dd = {key: value for (key, value) in d.items()} # 备份上一轮的d for u in g: for v in g[u]: # 内部的二重for循环遍历所有边 d[v] = min(d[v], dd[u] + g[u][v]) # 松弛 print(i+1, d) return d def bellman_ford(g, s): d = dict() for v in g: # 距离初始化为无穷大 前驱初始化为空 d[v] = float("inf") d[s] = 0 # 源点到自己的最短路径长度为零 print(0, d) for i in range(len(g) - 1): # 遍历|V|-1次 for u in g: for v in g[u]: # 内部的二重for循环遍历所有边 d[v] = min(d[v], d[u] + g[u][v]) # 松弛 print(i+1, d) return d g = { # 顺序遍历所有点 s -> x -> y -> z -> t "s": {"x": 1}, # w(s,x) = 1 "x": {"y": 1}, # w(x,y) = 1 "y": {"z": 1}, # w(y,z) = 1 "z": {"t": 1}, # w(z,t) = 1 "t": {} } print("顺序 使用上一轮的d") d = bellman_ford_dp(g, s="s") # --------------------------------------------------- # 顺序 使用上一轮的d # 0 {'s': 0, 'x': inf, 'y': inf, 'z': inf, 't': inf} # 1 {'s': 0, 'x': 1, 'y': inf, 'z': inf, 't': inf} # 2 {'s': 0, 'x': 1, 'y': 2, 'z': inf, 't': inf} # 3 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': inf} # 4 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': 4} print("顺序 使用即时的d") d = bellman_ford(g, s="s") # --------------------------------------------------- # 顺序 使用即时的d # 0 {'s': 0, 'x': inf, 'y': inf, 'z': inf, 't': inf} # 1 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': 4} # 2 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': 4} # 3 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': 4} # 4 {'s': 0, 'x': 1, 'y': 2, 'z': 3, 't': 4} g = { # 逆序遍历所有点 t -> z -> y -> x -> s "t": {}, "z": {"t": 1}, # w(z,t) = 1 "y": {"z": 1}, # w(y,z) = 1 "x": {"y": 1}, # w(x,y) = 1 "s": {"x": 1}, # w(s,x) = 1 } print("逆序 使用上一轮的d") d = bellman_ford_dp(g, s="s") # --------------------------------------------------- # 逆序 使用上一轮的d # 0 {'t': inf, 'z': inf, 'y': inf, 'x': inf, 's': 0} # 1 {'t': inf, 'z': inf, 'y': inf, 'x': 1, 's': 0} # 2 {'t': inf, 'z': inf, 'y': 2, 'x': 1, 's': 0} # 3 {'t': inf, 'z': 3, 'y': 2, 'x': 1, 's': 0} # 4 {'t': 4, 'z': 3, 'y': 2, 'x': 1, 's': 0} print("逆序 使用即时的d") d = bellman_ford(g, s="s") # --------------------------------------------------- # 逆序 使用即时的d # 0 {'t': inf, 'z': inf, 'y': inf, 'x': inf, 's': 0} # 1 {'t': inf, 'z': inf, 'y': inf, 'x': 1, 's': 0} # 2 {'t': inf, 'z': inf, 'y': 2, 'x': 1, 's': 0} # 3 {'t': inf, 'z': 3, 'y': 2, 'x': 1, 's': 0} # 4 {'t': 4, 'z': 3, 'y': 2, 'x': 1, 's': 0}
设所有点的最短路径边数最长的为$k$
动态规划 | 非动态规划 | |
---|---|---|
使用$d[]$表 | 上一轮 | 即时 |
点的更新顺序 | 没影响 | 有影响 |
最好情况 | $k$轮 | $1$轮,点的更新顺序恰是最短路径上的顺序 |
最坏情况 | $k$轮 | $k$轮,点的更新顺序恰是最短路径上的逆序 |
从$s$出发只有$t$和$y$一步能到,其它点至少需经过其中某个点
注意$w(s,t) = 6 < 7 = w(s,y)$,若做贪心选择,应该先选择$t$
但实际却是从$y$、$x$中转
从$y$出发后续有负权重的边可以再减少路径长度
若想贪心,不能有负权重的边
假设图中不再有负权重的边来减少路径长度
引入已确定最短路径的顶点集合$\Scal$,初始为空
贪心选择:从$\Vcal \setminus \Scal$中选择最短路径估计值最小的结点加入$\Scal$
$d_s = 0$、$d_y = d_t = d_x = d_z = \infty$
将$s$加入$\Scal$,$\Scal = \{ s \}$
$\Scal = \{ s \}$,根据$\delta(s,s) = 0$更新最短路径估计值
将$y$加入$\Scal$,$\Scal = \{ s, y \}$
$\Scal = \{ s, y \}$,根据$\delta(s,y) = 5 ~ (?)$更新最短路径估计值
将$z$加入$\Scal$,$\Scal = \{ s, y , z \}$,如此迭代下去
from queue import PriorityQueue def dijkstra(s): q, in_q, d, p = PriorityQueue(), dict(), dict(), dict() for v in g: # 距离初始化为无穷大 前驱初始化为空 in_q[v], d[v], p[v] = False, float("inf"), None in_q["s"], d["s"] = True, 0 q.put([0, "s"]) # 源点入队 while not q.empty(): _, u = q.get() # 获取队首元素点u in_q[u] = False # 更新点u的在队状态 for v in g[u]: # 更新u指向的点的最短路径 if d[v] > d[u] + g[u][v]: # 边(u,v)可以松弛 if in_q[v]: q.queue.remove([d[v], v]) d[v], p[v] = d[u] + g[u][v], u # 更新最短距离和前驱 q.put([d[v], v]) in_q[v] = True return d, p g = { "s": {"t": 10, "y": 5}, # w(s,t) = 6, w(s,y) = 5 "t": {"x": 1, "y": 2}, # w(t,x) = 1, w(t,y) = 2 "y": {"t": 3, "z": 2, "x": 9}, # w(y,t) = 3, w(y,z) = 2, w(y,x) = 9 "z": {"s": 7, "x": 6}, # w(z,s) = 7, w(z,x) = 6 "x": {"z": 4}, # w(x,z) = 4 } d, p = dijkstra("s") print(d) print(p) # --------------------------------------------------- # {'s': 0, 't': 8, 'y': 5, 'z': 7, 'x': 9} # {'s': None, 't': 'y', 'y': 's', 'z': 'y', 'x': 't'} g = { # dijkstra也是可以处理带负边的图的 "s": {"t": 6, "y": 7}, # w(s,t) = 6, w(s,y) = 7 "t": {"x": 5, "z": -4, "y": 8}, # w(t,x) = 5, w(t,z) = -4, w(t,y) = 8 "y": {"z": 9, "x": -3}, # w(y,z) = 9, w(y,x) = -3 "z": {"x": 7, "s": 2}, # w(z,x) = 7, w(z,s) = 2 "x": {"t": -2}, # w(x,t) = -2 } d, p = dijkstra("s") print(d) print(p) # --------------------------------------------------- # {'s': 0, 't': 2, 'y': 7, 'z': -2, 'x': 4} # {'s': None, 't': 'x', 'y': 's', 'z': 't', 'x': 'y'}
不变式:在外层 for 循环每次执行前,对$\forall u \in \Scal$有$d_u = \delta (s,u)$
初始$\Scal = \emptyset$,之后源点$s$第一个加入$\Scal$,显然$d_s = \delta (s,s) = 0$
设$u$是第一个加入$\Scal$时$d_u \ne \delta (s,u)$的点
此时必存在$s$到$u$的路径,否则$d_u = \delta (s,u) = \infty$,与假设矛盾
设$s$到$u$的最短路径为$p$
下面说明$u$是路径$p$上第一个 (也是唯一一个) 不在$\Scal$中的点
否则设$u$前还有$y \not \in \Scal$,由于$d_y < d_u$,应该加入的是$y$不是$u$
设$u$的前驱$x \in \Scal$,$x$在$u$之前加入$\Scal$,因此$d_x = \delta(s,x)$
在$x$加入$\Scal$时会更新$x$指向的所有点的最短路径估计值
$d_u = d_x + w(x,u) = \delta(s,x) + w(x,u)$
根据最优子结构性,$d_u = \delta(s,u)$
| 方法 | 条件 | 实现 | 一般时间复杂度 |
---|---|---|---|---|
Bellman-Ford | 动态规划 | 可以有负边 | | $\Theta(\shu\Vcal\shu \shu\Ecal\shu)$ |
Dijkstra | 贪心法 | 必须无负边 | 线性数组 | $\Theta(\shu\Vcal\shu^2 + \shu\Ecal\shu)$ |
二叉堆 | $\Theta((\shu\Vcal\shu + \shu\Ecal\shu) \lg \shu\Vcal\shu)$ | |||
斐波那契堆 | $\Theta(\shu\Vcal\shu \lg \shu\Vcal\shu + \shu\Ecal\shu)$ |
Bellman-Ford 算法可检测负环,进一步考虑图的稀疏性有
| 实现 | 一般时间复杂度 | 稠密图 | 稀疏图 |
---|---|---|---|---|
Bellman-Ford | | $\Theta(\shu\Vcal\shu \shu\Ecal\shu)$ | $\Theta(\shu\Vcal\shu^3)$ | $O(\shu\Vcal\shu^2)$ |
Dijkstra | 线性数组 | $\Theta(\shu\Vcal\shu^2 + \shu\Ecal\shu)$ | $\Theta(\shu\Vcal\shu^2)$ | $\Theta(\shu\Vcal\shu^2)$ |
二叉堆 | $\Theta((\shu\Vcal\shu + \shu\Ecal\shu) \lg \shu\Vcal\shu)$ | $\Theta(\shu\Vcal\shu^2 \lg \shu\Vcal\shu)$ | $\Theta(\shu\Vcal\shu \lg \shu\Vcal\shu)$ | |
斐波那契堆 | $\Theta(\shu\Vcal\shu \lg \shu\Vcal\shu + \shu\Ecal\shu)$ | $\Theta(\shu\Vcal\shu^2)$ | $\Theta(\shu\Vcal\shu \lg \shu\Vcal\shu)$ |
假设一个生产工序有$n$个步骤,在时刻$x_i$进行第$i$个步骤
步骤的执行时间会有一些约束
在时刻$x_i$使用一种需要$2$个小时才能风干的粘贴剂材料
下一个步骤需要$2$小时后等粘贴剂干了才能在时刻$x_{i+1}$安装
这样就有约束条件$x_i - x_{i+1} \le -2$
把所有的约束条件写到一起就是差分约束系统
$$ \begin{align*} \quad \begin{cases} x_1 - x_2 \le 0 \\ x_1 - x_5 \le -1 \\ x_2 - x_5 \le 1 \\ x_3 - x_1 \le 5 \\ x_4 - x_1 \le 4 \\ x_4 - x_3 \le -1 \\ x_5 - x_3 \le -3 \\ x_5 - x_4 \le -3 \end{cases} \quad \Longleftrightarrow \quad \underbrace{\begin{bmatrix} 1 & -1 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & -1 \\ 0 & 1 & 0 & 0 & -1 \\ -1 & 0 & 1 & 0 & 0 \\ -1 & 0 & 0 & 1 & 0 \\ 0 & 0 & -1 & 1 & 0 \\ 0 & 0 & -1 & 0 & 1 \\ 0 & 0 & 0 & -1 & 1 \end{bmatrix}}_{\Av ~ \in ~ \{ \pm 1, 0 \}^{m \times n}} \underbrace{\begin{bmatrix} x_1 \\ x_2 \\ x_3 \\ x_4 \\ x_5 \end{bmatrix}}_{\xv ~ \in ~ \Rbb^n} \le \underbrace{\begin{bmatrix} 0 \\ -1 \\ 1 \\ 5 \\ 4 \\ -1 \\ -3 \\ -3 \end{bmatrix}}_{\bv ~ \in ~ \Rbb^m} \end{align*} $$
满足$\Av \xv \le \bv$的$\xv$称为差分约束系统的解
差分约束系统的解不唯一,若$\xv$是解,则$\xv + c \onev$也是解
给定差分约束系统$\Av \xv \le \bv$,其约束图是带权有向图$\Gcal = (\Vcal, \Ecal)$
$$ \begin{align*} \qquad \begin{cases} x_1 - x_2 \le 0 \\ x_1 - x_5 \le -1 \\ x_2 - x_5 \le 1 \\ x_3 - x_1 \le 5 \\ x_4 - x_1 \le 4 \\ x_4 - x_3 \le -1 \\ x_5 - x_3 \le -3 \\ x_5 - x_4 \le -3 \end{cases} \quad \Longrightarrow \end{align*} $$
给定差分约束系统$\Av \xv \le \bv$,其约束图是带权有向图$\Gcal = (\Vcal, \Ecal)$
若$\Gcal$包含负环,系统无解,否则有解$\xv = [\delta(v_0, v_1), \ldots, \delta(v_0, v_n)]$
设负环为$v_i, v_{i+1}, \ldots, v_{j-1}, v_j$,其中$v_j = v_i$,则
$$ \begin{align*} \quad (v_i, v_{i+1}) & \Longleftrightarrow x_{i+1} - x_i \le w(v_i, v_{i+1}) \\ (v_{i+1}, v_{i+2}) & \Longleftrightarrow x_{i+2} - x_{i+1} \le w(v_{i+1}, v_{i+2}) \\ & \quad \vdots \\ (v_{j-1}, v_j) & \Longleftrightarrow x_j - x_{j-1} \le w(v_{j-1}, v_j) \end{align*} $$
注意$x_j = x_i$,累加可得$0 \le \sum_{k=i}^{j-1} w(v_k,v_{k+1}) \overset{\text{负环}}{<} 0$
给定差分约束系统$\Av \xv \le \bv$,其约束图是带权有向图$\Gcal = (\Vcal, \Ecal)$
若$\Gcal$包含负环,系统无解,否则有解$\xv = [\delta(v_0, v_1), \ldots, \delta(v_0, v_n)]$
对$\forall (v_i, v_j) \in \Ecal$,对应约束$x_j - x_i \le w(v_i, v_j)$,根据三角不等式
$$ \begin{align*} \quad \delta(v_0, v_j) \le \delta(v_0, v_i) + w(v_i, v_j) \Longrightarrow \delta(v_0, v_j) - \delta(v_0, v_i) \le w(v_i, v_j) \end{align*} $$
取$x_j = \delta(v_0, v_j)$、$x_i = \delta(v_0, v_i)$即可
综上,可对其约束图以$v_0$为源点运行 Bellman-Ford 算法
$$ \begin{align*} \qquad \begin{cases} x_1 - x_2 \le 0 \\ x_1 - x_5 \le -1 \\ x_2 - x_5 \le 1 \\ x_3 - x_1 \le 5 \\ x_4 - x_1 \le 4 \\ x_4 - x_3 \le -1 \\ x_5 - x_3 \le -3 \\ x_5 - x_4 \le -3 \end{cases} \quad \Longrightarrow \end{align*} $$
$\xv = [-5, -3, 0, -1, -4]$就是一个解