惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AI
AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google DeepMind News
Google DeepMind News
T
Tenable Blog
博客园_首页
S
Securelist
Spread Privacy
Spread Privacy
Google Online Security Blog
Google Online Security Blog
Forbes - Security
Forbes - Security
Engineering at Meta
Engineering at Meta
U
Unit 42
L
LINUX DO - 热门话题
量子位
T
Threat Research - Cisco Blogs
博客园 - 【当耐特】
C
Cyber Attacks, Cyber Crime and Cyber Security
K
Kaspersky official blog
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
Google DeepMind News
Google DeepMind News
GbyAI
GbyAI
Martin Fowler
Martin Fowler
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Security Latest
Security Latest
Scott Helme
Scott Helme
V
Vulnerabilities – Threatpost
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
I
InfoQ
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
The Register - Security
The Register - Security
T
The Blog of Author Tim Ferriss
aimingoo的专栏
aimingoo的专栏
V2EX - 技术
V2EX - 技术
T
Tailwind CSS Blog
月光博客
月光博客
Recent Announcements
Recent Announcements
G
Google Developers Blog
F
Full Disclosure
W
WeLiveSecurity
宝玉的分享
宝玉的分享
腾讯CDC
G
GRAHAM CLULEY
Vercel News
Vercel News
Simon Willison's Weblog
Simon Willison's Weblog
美团技术团队
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Help Net Security
Help Net Security

博客园 - RonChen

同余分析 Treap 点分治 莫队算法 分块 扫描线 错排问题 Sprague-Grundy (SG) 函数及其应用 容斥原理 卢卡斯定理 线性基 高斯消元 勒让德公式 次短路 分层图最短路 01 图最短路 洪水填充 双向搜索 迭代加深搜索 剪枝 最小表示法 表达式计算 KMP 算法 队列 高精度运算
差分约束
RonChen · 2026-05-29 · via 博客园 - RonChen

差分约束系统用于求解 \(n\) 个未知数 \(x_1 \sim x_n\)\(m\) 个形如 \(x_u - x_v \le w\) 也就是 \(x_u \le x_v + w\) 的不等式的解。

两种建图方式:

  1. \(v\)\(u\) 连边权为 \(w\) 的有向边,建立虚点 \(0\) 向每个点连边权为 \(0\) 的有向边,从点 \(0\) 开始跑最短路,得到的是每个 \(x_u \le 0\)\(x_u\) 的最大值,如果图中有负环则无解。
  2. \(u\)\(v\) 连边权为 \(-w\) 的有向边,建立虚点 \(0\) 向每个点连边权为 \(0\) 的有向边,从点 \(0\) 开始跑最长路,得到的是每个 \(x_u \ge 0\)\(x_u\) 的最大值,如果图中有负环则无解。

例题:P5960 【模板】差分约束

参考代码
#include <cstdio>
#include <vector>
#include <queue>
#include <utility>
const int N = 5005;
const int INF = 1e9;
std::vector<std::pair<int, int>> g[N];
int cnt[N], dis[N];
bool inq[N];
int main()
{
    int n, m; scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int c1, c2, y;
        scanf("%d%d%d", &c1, &c2, &y);
        g[c2].push_back({c1, y});
    }
    for (int i = 1; i <= n; i++) {
        dis[i] = INF; 
        g[0].push_back({i, 0});
    }
    std::queue<int> q;
    q.push(0); dis[0] = 0; inq[0] = true;
    while (!q.empty()) {
        int u = q.front(); q.pop(); inq[u] = false;
        for (auto e : g[u]) {
            int v = e.first, w = e.second;
            if (dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                cnt[v] = cnt[u] + 1;
                if (cnt[v] >= n + 1) { // 注意多了一个虚点0
                    printf("NO\n"); return 0;
                }
                if (!inq[v]) {
                    q.push(v); inq[v] = true;
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) printf("%d ", dis[i]);
    return 0;
}

例题:P1993 小 K 的农场

  1. \(x_a - x_b \ge c\) 实际上就是 \(x_b - x_a \le -c\),可以让 \(a\)\(b\) 连一条边权为 \(-c\) 的边
  2. \(x_a - x_b \le c\) 是差分约束系统的标准形式,让 \(b\)\(a\) 连一条边权为 \(c\) 的边
  3. \(x_a = x_b\) 也可以转化成不等式关系,即 \(x_b - x_a \le 0\)\(x_a - x_b \le 0\),让 \(a\)\(b\) 之间互相连边权为 \(0\) 的边
参考代码
#include <cstdio>
#include <vector>
#include <utility>
#include <queue>
const int N = 5005;
const int INF = 1e9;
std::vector<std::pair<int, int>> g[N];
int cnt[N], dis[N];
bool inq[N];
int main()
{
    int n, m; scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int t, a, b, c; scanf("%d%d%d", &t, &a, &b);
        if (t != 3) scanf("%d", &c);
        if (t == 1) g[a].push_back({b, -c});
        else if (t == 2) g[b].push_back({a, c});
        else {
            g[a].push_back({b, 0}); g[b].push_back({a, 0});
        }
    }
    for (int i = 1; i <= n; i++) {
        g[0].push_back({i, 0}); dis[i] = INF;
    }
    std::queue<int> q;
    q.push(0); inq[0] = true; dis[0] = 0;
    while (!q.empty()) { 
        int u = q.front(); q.pop(); inq[u] = false;
        for (auto e : g[u]) {
            int v = e.first, w = e.second;
            if (dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                cnt[v] = cnt[u] + 1;
                if (cnt[v] >= n + 1) {
                    printf("No\n"); return 0;
                }
                if (!inq[v]) {
                    q.push(v); inq[v] = true;
                }
            }
        }
    }
    printf("Yes\n");
    return 0;
}

习题:P1260 工程规划

给定包含 \(n \ (5 \le n \le 1000)\) 个子任务的工程,每个任务的起始时间为 \(T_1, T_2, \dots, T_n\)(均为非负整数,代表整个工程开始后的启动时间)。另外给定 \(m \ (5 \le m \le 5000)\) 个限制条件,每个限制条件形如不等式 \(T_i - T_j \le b\)

任务是:

  1. 找出一种可能的起始时间序列 \(T_1, T_2, \dots, T_n\)
  2. 要求最早进行的任务起始时间必须与整个工程的起始时间相同,即 \(T_1, T_2, \dots, T_n\) 中至少有一个为 \(0\)
  3. 若无可行方案,输出 NO SOLUTION
解题思路

\(T_i - T_j \le b\) 可以变形为 \(T_i \le T_j + b\),这与图论中单源最短路的三角不等式 \(d(v) \le d(u) + w(u,v)\) 形式完全一致。因此,可以将每个任务看作图中的一个顶点:对于每个不等式 \(T_i - T_j \le b\),从顶点 \(j\) 向顶点 \(i\) 连一条有向边,边的权值为 \(b\);如果图中存在 负权环,则说明不等式组无解,输出 NO SOLUTION

由于图可能不是强连通的,引入一个 超级源点 \(0\)。从超级源点 \(0\) 向每个任务节点 \(1 \le i \le n\) 连一条边权为 \(0\) 的有向边,代表不等式 \(T_i - T_0 \le 0\)。设定 \(T_0 = 0\),这样在求最短路时,所有的最短路初始上限都会被约束在 \(0\) 以下,即 \(T_i \le 0\)。同时,从源点 \(0\) 出发可以遍历到图中的所有节点,从而能够检测出图中的任意负权环。

如果未检测到负环,会求得一组解 \(d_1, d_2, \dots, d_n\)。以超级源点为基准算出的最短路值 \(d_i \le 0\),而答案需要满足所有的起始时间均为非负整数(\(T_i \ge 0\))和至少有一个任务的起始时间为 \(0\)。因为差分约束系统的解具有 平移不变性(即若 \(T\) 是一组解,则对任意常数 \(C\)\(T+C\) 也是一组解),可以找出这组解中的最小值 \(\min \{ d \}\),然后将每个节点的起始时间修正为 \(T_i = d_i - \min \{ d \}\)。这样修正后的解不仅满足所有差分约束条件,而且保证了所有起始时间均非负,且至少有一个(即取得最小值的节点)起始时间为 \(0\)

参考代码
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 1005;
const int INF = 1e9;
struct Edge {
    int to, w;
};
vector<Edge> a[N];
int d[N], cnt[N];
bool inq[N];
int main()
{
    int n, m; scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        // T_u - T_v <= w  =>  T_u <= T_v + w
        // 建立从 v 到 u,边权为 w 的有向边
        a[v].push_back({u, w});
    }
    for (int i = 1; i <= n; i++) {
        // 增加超级源点 0,向每个顶点 i 连一条权值为 0 的边
        a[0].push_back({i, 0});
        d[i] = INF;
    }
    queue<int> q;
    q.push(0); inq[0] = true;
    while (!q.empty()) {
        int u = q.front(); q.pop(); inq[u] = false;
        for (Edge e : a[u]) {
            int v = e.to, w = e.w;
            if (d[u] + w < d[v]) {
                d[v] = d[u] + w;
                cnt[v] = cnt[u] + 1;
                if (cnt[v] > n) {
                    printf("NO SOLUTION\n");
                    return 0;
                }
                if (!inq[v]) {
                    q.push(v); inq[v] = true;
                }
            }
        }
    }
    int mind = INF;
    for (int i = 1; i <= n; i++) mind = min(mind, d[i]);
    for (int i = 1; i <= n; i++) printf("%d\n", d[i] - mind);
    return 0;
}