差分约束
一、负环
1. 定义
在一张带权有向图中,若图中存在一个环,且环上各边权值之和为负数,则称此环为 负环 ;
一旦出现负环,则每条最短路每经过一次负环,最短路就会减小,若走无数次,则不再存在最短路;
2. SPFA 判定负环
在 SPFA 算法中,每个点最多被其他 n − 1 n - 1 n−1 个点更新一次,所以如果被更新了 n n n 次,那么则说明原图存在负环;
struct edge {
int to, tot;
};
vector <edge> g[MAXN];
int dis[MAXN], tot1[MAXN];
bool vis[MAXN];
bool SPFA(int s) {
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
queue <int> q;
q.push(s);
while (!q.empty()) {
int t = q.front();
q.pop();
vis[t] = false;
for (int i = 0; i < g[t].size(); i++) {
int v = g[t][i].to, tot = g[t][i].tot;
if (dis[v] > dis[t] + tot) {
dis[v] = dis[t] + tot;
if (!vis[v]) {
vis[v] = true;
tot1[v]++;
if (tot1[v] == n) return false; // 更新 n 次,有负环,退出
q.push(v);
}
}
}
}
return true;
}
二、差分约束系统
1. 问题类型
差分约束系统是一种特殊的 n n n 元一次不等式组;
其为有 n n n 个变量 x 1 , x 2 , … , x n x_1, x_2, \dots , x_n x1,x2,…,xn 及 m m m 个形如 x i − x j ≤ c k x_i - x_j \leq c_k xi−xj≤ck 的不等式,其中 1 ≤ i , j ≤ n 1 \leq i, j \leq n 1≤i,j≤n , c k c_k ck 为常数,组成的不等式组;
求一组解 x 1 = a 1 , x 2 = a 2 , … , x n = a n x_1 = a_1, x_2 = a_2, \dots , x_n = a_n x1=a1,x2=a2,…,xn=an ,使得其满足不等式的所有约束条件;
2. 解决
对于不等式,可有以下变形,
x
i
−
x
j
≤
c
k
x
i
≤
c
k
+
x
j
\begin{aligned} x_i - x_j &\leq c_k \\ x_i &\leq c_k + x_j \\ \end{aligned}
xi−xjxi≤ck≤ck+xj
可发现,此不等式与最短路中的三角不等式,即
d
i
s
[
y
]
≤
d
i
s
[
x
]
+
w
(
x
,
y
)
dis[y] \leq dis[x] + w(x, y)
dis[y]≤dis[x]+w(x,y)
十分相似;
所以,可以对于每一个不等式,建 w ( j , i ) = c k w(j, i) = c_k w(j,i)=ck 这条边;
又由于若 { a 1 , x 2 = a 2 , … , x n = a n } \{a_1, x_2 = a_2, \dots , x_n = a_n\} {a1,x2=a2,…,xn=an} 为一组解,则对于任意常数 Δ \Delta Δ , x 1 = a 1 + Δ , x 2 = a 2 + Δ , … , x n = a n + Δ x_1 = a_1 + \Delta , x_2 = a_2 + \Delta, \dots , x_n = a_n + \Delta x1=a1+Δ,x2=a2+Δ,…,xn=an+Δ 也为一组解;
所以,可先求出一组负数特解,再找到合适的范围;
建一个超级源点 0 ,并向所有点连边,即对 i ∈ [ 1 , n ] i \in [1, n] i∈[1,n] 添加 a i − a 0 ≤ 0 a_i - a_0 \leq 0 ai−a0≤0 的约束条件,再以 0 为原点开始跑单源最短路,则每个点到 0 的最短路径长度则可成为一组解;
若图中存在负环,则不存在最短路,也无法满足三角不等式关系,所以无解;
3. 扩展
-
对于形如 x i − x j ≥ c k x_i - x_j \geq c_k xi−xj≥ck 的不等式,有
x i − x j ≥ c k x j − x i ≤ − c k x j ≤ − c k + x i \begin{aligned} x_i - x_j &\geq c_k \\ x_j - x_i &\leq -c_k \\ x_j &\leq -c_k + x_i \\ \end{aligned} xi−xjxj−xixj≥ck≤−ck≤−ck+xi
则建 w ( i , j ) = − c k w(i, j) = -c_k w(i,j)=−ck 的约束边即可; -
对于形如 x i − x j = c k x_i - x_j = c_k xi−xj=ck 的不等式,有
{ x i − x j ≤ c k x i − x j ≥ c k \left\{ \begin{aligned} x_i - x_j \leq c_k \\ x_i - x_j \geq c_k \\ \end{aligned} \right. {xi−xj≤ckxi−xj≥ck
则建 w ( i , j ) = − c k , w ( j , i ) = c k w(i, j) = -c_k, w(j, i) = c_k w(i,j)=−ck,w(j,i)=ck 的约束边即可; -
对于形如 x i − x j < c k x_i - x_j < c_k xi−xj<ck (解为整数)的不等式,有
x i − x j < c k x i − x j ≤ c k − 1 \begin{aligned} x_i - x_j &< c_k \\ x_i - x_j &\leq c_k - 1 \\ \end{aligned} xi−xjxi−xj<ck≤ck−1
则建 w ( j , i ) = c k − 1 w(j, i) = c_k - 1 w(j,i)=ck−1 的约束边即可;
4. 代码
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
int n, m;
struct edge {
int to, tot;
};
vector <edge> g[MAXN];
int dis[MAXN], tot1[MAXN];
bool vis[MAXN];
bool SPFA(int s) {
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
queue <int> q;
q.push(s);
while (!q.empty()) {
int t = q.front();
q.pop();
vis[t] = false;
for (int i = 0; i < g[t].size(); i++) {
int v = g[t][i].to, tot = g[t][i].tot;
if (dis[v] > dis[t] + tot) {
dis[v] = dis[t] + tot;
if (!vis[v]) {
vis[v] = true;
tot1[v]++;
if (tot1[v] == n) return false; // 判断无解
q.push(v);
}
}
}
}
return true;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) g[0].push_back(edge({i, 0})); // 建源点
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d %d %d", &x, &y, &z);
g[y].push_back(edge({x, z})); // 建边
}
if (SPFA(0)) for (int i = 1; i <= n; i++) printf("%d ", dis[i]);
else printf("NO\n");
return 0;
}
三、例题
矩阵游戏
1. 题目
有一个 n × m n \times m n×m 的矩阵 a i , j a_{i, j} ai,j ,有 0 ≤ a i , j ≤ 1 e 6 0 \leq a_{i,j} \leq 1e6 0≤ai,j≤1e6 ,现根据该矩阵生成了一个 ( n − 1 ) × ( m − 1 ) (n - 1) \times (m - 1) (n−1)×(m−1) 的矩阵 b i , j b_{i, j} bi,j ,且有,
b
i
,
j
=
a
i
,
j
+
a
i
,
j
+
1
+
a
i
+
1
,
j
+
a
i
+
1
,
j
+
1
b_{i, j} = a_{i, j} + a_{i, j + 1} + a_{i + 1, j} + a_{i + 1, j + 1}
bi,j=ai,j+ai,j+1+ai+1,j+ai+1,j+1
现给出的矩阵
b
i
,
j
b_{i, j}
bi,j ,还原出
a
i
,
j
a_{i, j}
ai,j ;
2. 分析
首先,若将 a a a 矩阵的 n n n 行与 m m m 列均设为 0 ,则可构造出一个只满足 b b b 矩阵,但不满足 a i , j a_{i, j} ai,j 范围的矩阵;
则应调整每个元素大小;
则问题转化为寻找一种修改方式,使得 a a a 矩阵仍满足 b b b 矩阵,但改变了每个元素大小;
可对每行或每列的元素进行如下修改符合上述要求,其中
Δ
\Delta
Δ 为常数;
[
+
Δ
,
−
Δ
,
+
Δ
,
…
]
\left[ \begin{matrix} +\Delta, \ -\Delta, \ +\Delta ,\ \dots\\ \end{matrix} \right]
[+Δ, −Δ, +Δ, …]
则可设对于
i
i
i 行,其修改值为
c
[
i
]
c[i]
c[i] ,对于
j
j
j 列,其修改值为
d
[
j
]
d[j]
d[j] ;
所以根据
a
i
,
j
a_{i, j}
ai,j 范围可列不等式,
0
≤
a
i
,
j
±
c
i
±
d
j
≤
1
e
6
0 \leq a_{i, j} \ \pm \ c_i \ \pm \ d_j \leq 1e6
0≤ai,j ± ci ± dj≤1e6
发现当
c
i
c_i
ci 与
d
j
d_j
dj 异号时,原不等式组可做差分约束条件;
则问题又转化为寻找一种修改方式,使得每个元素 a i , j a_{i, j} ai,j ,有修改的 c i c_i ci 与 d j d_j dj 异号;
则使行列修改方式不同即可,如下
行
[
+
Δ
,
−
Δ
,
+
Δ
,
−
Δ
,
…
−
Δ
,
+
Δ
,
−
Δ
,
+
Δ
,
…
+
Δ
,
−
Δ
,
+
Δ
,
−
Δ
,
…
−
Δ
,
+
Δ
,
−
Δ
,
+
Δ
,
…
]
行\left[ \begin{matrix} +\Delta, \ -\Delta, \ +\Delta ,\ -\Delta, \ \dots \\ -\Delta, \ +\Delta, \ -\Delta ,\ +\Delta, \ \dots \\ +\Delta, \ -\Delta, \ +\Delta ,\ -\Delta, \ \dots \\ -\Delta, \ +\Delta, \ -\Delta ,\ +\Delta, \ \dots \\ \end{matrix} \right]
行
+Δ, −Δ, +Δ, −Δ, …−Δ, +Δ, −Δ, +Δ, …+Δ, −Δ, +Δ, −Δ, …−Δ, +Δ, −Δ, +Δ, …
列 [ − Δ , + Δ , − Δ , + Δ , … + Δ , − Δ , + Δ , − Δ , … − Δ , + Δ , − Δ , + Δ , … + Δ , − Δ , + Δ , − Δ , … ] 列\left[ \begin{matrix} -\Delta, \ +\Delta, \ -\Delta ,\ +\Delta, \ \dots \\ +\Delta, \ -\Delta, \ +\Delta ,\ -\Delta, \ \dots \\ -\Delta, \ +\Delta, \ -\Delta ,\ +\Delta, \ \dots \\ +\Delta, \ -\Delta, \ +\Delta ,\ -\Delta, \ \dots \end{matrix} \right] 列 −Δ, +Δ, −Δ, +Δ, …+Δ, −Δ, +Δ, −Δ, …−Δ, +Δ, −Δ, +Δ, …+Δ, −Δ, +Δ, −Δ, …
即可转化为差分约束问题;
3. 代码
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 605;
const int MAX = 1000000;
int n, m;
long long a[MAXN][MAXN], b[MAXN][MAXN];
struct edge {
int to;
long long tot;
};
vector <edge> g[MAXN];
long long dis[MAXN], tot1[MAXN];
bool vis[MAXN];
bool SPFA(int s) { // 差分约束
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(tot1, 0, sizeof(tot1));
dis[s] = 0;
queue <int> q;
q.push(s);
while (!q.empty()) {
int t = q.front();
q.pop();
vis[t] = false;
for (int i = 0; i < g[t].size(); i++) {
int v = g[t][i].to;
long long tot = g[t][i].tot;
if (dis[v] > dis[t] + tot) {
dis[v] = dis[t] + tot;
if (!vis[v]) {
vis[v] = true;
tot1[v]++;
if (tot1[v] == n) return true;
q.push(v);
}
}
}
}
return false;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
scanf("%d %d", &n, &m);
for (int i = 1; i <= n - 1; i++) {
for (int j = 1; j <= m - 1; j++) {
scanf("%lld", &b[i][j]);
}
}
for (int i = n; i >= 1; i--) { // 求初始矩阵
for (int j = m; j >= 1; j--) {
if (i == 0 || j == 0) a[i][j] = 0;
else a[i][j] = b[i][j] - a[i + 1][j] - a[i][j + 1] - a[i + 1][j + 1];
}
}
for (int i = 1; i <= n; i++) { // 添加约束条件
for (int j = 1; j <= m; j++) {
if ((i + j) % 2) {
g[i].push_back(edge({j + n, MAX - a[i][j]}));
g[j + n].push_back(edge({i, a[i][j]}));
} else {
g[j + n].push_back(edge({i, MAX - a[i][j]}));
g[i].push_back(edge({j + n, a[i][j]}));
}
}
}
if (SPFA(1)) {
printf("NO\n");
for (int i = 1; i <= n + m; i++) g[i].clear();
continue;
}
printf("YES\n");
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if ((i + j) % 2) printf("%lld ", a[i][j] + dis[j + n] - dis[i]);
else printf("%lld ", a[i][j] - dis[j + n] + dis[i]);
}
printf("\n");
}
for (int i = 1; i <= n + m; i++) g[i].clear();
}
return 0;
}