讲课:差分约束
讲课:差分约束
为甚么不是讲最短路的同学来讲这个……
差分约束系统是一个关于变量 \(\vec{x} = (x_i)_{i = 0}^{n - 1}\) 的 \(n\) 元一次不等式组,他由 \(m\) 个形如 \(x_j - x_i \leq l_k\ (i,j,k \in \mathbb{N},\ i,j < n,\ i \neq j,\ k < m,\ l_k \in \mathbb{R})\) 的不等式组成。
给定这样一个差分约束系统,我们需要求出可行解(通常只需求一组解),或报告无解。
对于每个约束条件 \(x_j - x_i \leq l_k \iff x_j \leq x_i + l_k\),他变形后长得比较像最短路中的松弛条件 \(\delta(s, v) > \delta(s, u) + w\)(\(s\) 是源点)。当我们的单源最短路算法求解完成之后,对每一条边 \((u, v, w)\) 都有 \(\delta(s, v) \leq \delta(s, u) + w\),正是我们现在的约束条件的形式。
因此,我们只需对每个 \(x_j - x_i \leq l_k\),建边 \((i, j, l_k)\)。不过此时图可能不连通,所以我们可以建一个超级源点 \(s\),分别将 \(s\) 与所有 \(i \in \mathbb{N} \cap [0, n)\) 建边 \((s, i, 0)\)(相当于追加了 \(n\) 个不等式 \(x_s \geq x_i\ (i \in \mathbb{N},\ i < n)\),不影响结果)。对 \(s\) 求单源最短路,任意赋值 \(x_s\),\((x_s + \delta(s, i))_{i = 0}^{n - 1}\) 就是一组可行解。不过,当图中存在负环的时候,是没办法求出单源最短路的,此时该不等式组会要求环上点代表的变量自己比自己小——这当然是不可能的,也就是不等式组无解。
为了正确处理负环,推荐使用 SPFA 算法计算单源最短路。
模板题 Luogu P1993 参考代码:
template <typename Weight>
inline std::vector<Weight>
spfa(const std::vector<std::vector<std::pair<size_t, Weight>>> &adj, // {to, weight}
size_t start) {
size_t n = adj.size();
if (!n) {
return {};
}
auto dist = std::vector<Weight>(n, INT32_MAX);
auto inq = std::vector<bool>(n);
auto cnt = std::vector<size_t>(n);
dist[start] = 0;
std::queue<size_t> q;
q.emplace(start);
while (q.size()) {
auto u = q.front();
inq[u] = false;
q.pop();
for (auto &e : adj[u]) {
auto v = e.first;
auto w = e.second;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
/**
* the shortest path between 2 vertices consists of at most
* (n - 1) edgess
*/
if ((cnt[v] = cnt[u] + 1) >= n) {
return {};
}
if (!inq[v]) {
inq[v] = true;
q.emplace(v);
}
}
}
}
return dist;
}
inline void solve() {
size_t n, m;
std::cin >> n >> m;
auto adj = std::vector<std::vector<std::pair<size_t, int32_t>>>(n + 1);
for (size_t i = 0; i != m; ++i) {
int op;
size_t u, v;
int32_t w;
std::cin >> op >> v >> u;
--u, --v;
if (op == 3) {
adj[u].emplace_back(v, 0);
adj[v].emplace_back(u, 0);
} else {
std::cin >> w;
if (op == 1) {
std::swap(u, v);
w = -w;
}
adj[u].emplace_back(v, w);
}
}
adj[n].reserve(n);
for (size_t i = 0; i != n; ++i) {
adj[n].emplace_back(i, 0);
}
// auto dists = spfa(adj, n);
// if (dists.size()) {
// for (auto [v, w] : adj[n]) {
// assert(dists[v] <= 0);
// }
// }
std::cout << (spfa(adj, n).empty() ? "No\n" : "Yes\n");
}
时间复杂度:取决于最短路算法;对于 SPFA,其在随机图的复杂度为 \(O(k \cdot |E|)\)(\(k\) 是常数),但在最坏情况下为 \(O(|V| \cdot |E|)\)。
空间复杂度:\(\Theta(|V|)\) 额外空间。

浙公网安备 33010602011771号