差分约束 总结

差分约束

一、负环

1. 定义

在一张带权有向图中,若图中存在一个环,且环上各边权值之和为负数,则称此环为 负环 ;

一旦出现负环,则每条最短路每经过一次负环,最短路就会减小,若走无数次,则不再存在最短路;

2. SPFA 判定负环

在 SPFA 算法中,每个点最多被其他 n − 1 n - 1 n1 个点更新一次,所以如果被更新了 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 xixjck 的不等式,其中 1 ≤ i , j ≤ n 1 \leq i, j \leq n 1i,jn 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} xixjxickck+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 aia00 的约束条件,再以 0 为原点开始跑单源最短路,则每个点到 0 的最短路径长度则可成为一组解;

若图中存在负环,则不存在最短路,也无法满足三角不等式关系,所以无解;

3. 扩展

  1. 对于形如 x i − x j ≥ c k x_i - x_j \geq c_k xixjck 的不等式,有
    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} xixjxjxixjckckck+xi
    则建 w ( i , j ) = − c k w(i, j) = -c_k w(i,j)=ck 的约束边即可;

  2. 对于形如 x i − x j = c k x_i - x_j = c_k xixj=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. {xixjckxixjck
    则建 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 的约束边即可;

  3. 对于形如 x i − x j < c k x_i - x_j < c_k xixj<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} xixjxixj<ckck1
    则建 w ( j , i ) = c k − 1 w(j, i) = c_k - 1 w(j,i)=ck1 的约束边即可;

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 0ai,j1e6 ,现根据该矩阵生成了一个 ( n − 1 ) × ( m − 1 ) (n - 1) \times (m - 1) (n1)×(m1) 的矩阵 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 0ai,j ± ci ± dj1e6
发现当 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;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值