目录

    讲课:差分约束

    讲课:差分约束

    为甚么不是讲最短路的同学来讲这个……

    差分约束系统是一个关于变量 \(\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|)\) 额外空间。

    posted @ 2026-08-16 02:47  我就是蓬蒿人  阅读(11)  评论(0)    收藏  举报