差分约束系统

差分约束系统

一、定义

如果一个系统由n个变量和m个约束条件组成,形成m个形如ai-aj≤k的不等式(i,j∈[1,n],k为常数),则称其为差分约束系统(system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。

观察xj-xi<=bk,会发现它类似最短路中的三角不等式d[v]<=d[u]+w[u,v],即d[v]-d[u]<=w[u,v] [1] 。因此,以每个变量xi为结点,对于约束条件xj-xi<=bk,连接一条边(i,j),边权为bk。我们再增加一个源点s,s与所有定点相连,边权均为0。对这个图,以s为源点运行Bellman-ford算法(或SPFA算法),最终{d[ i]}即为一组可行解。

二、实现

求解差分约束系统,可以转化成图论的单源最短路径(或最长路径)问题。
并且,在实际应用中,多使用SPFA算法实现。

那么如果在一个未知数定死的情况下,要求其它所有未知数的最小值怎么办?只要反过来求最长路径就可以了。最长路径中的三角不等式与最短路径中相反:

d(v) >= d(u) + w(u, v)
也就是 d(v) - d(u) >= w(u, v)

所以建图的时候要先把所有不等式化成大于等于号的。其它各种过程,包括证明为什么解出的是最小值的证法,都完全类似。

三、应用

总之对于不同的题目,给出的条件都不一样,我们首先需要关注问题是什么,

①求 xn-x1 的最大值:
将所有不等式化为 a-b<=w 的形式,对每个不等式建边:b 到 a 权值为 w

②求 xn-x1 的最小值
将所有不等式化为 a-b>=w 的形式,对每个不等式建边:b 到 a 权值为-w

四、不等式组的转化

做题时可能会遇到不等式中的符号不相同的情况,但我们可以对它们进行适当的转化

(1)方程给出:X[n-1]-X[0]>=T ,可以进行移项转化为: X[0]-X[n-1]<=-T。

(2)方程给出:X[n-1]-X[0]<T, 可以转化为X[n-1]-X[0]<=T-1。

(3)方程给出:X[n-1]-X[0]=T,可以转化为X[n-1]-X[0]<=T&&X[n-1]-X[0]>=T,再利用(1)进行转化即可

例题:

题目描述

Bessie 在过去的 M 天内参加了 N 次挤奶。但她已经忘了她每次挤奶是在哪个时候了。

对于第 i 次挤奶,Bessie 记得它不早于第 Si​ 天进行。另外,她还有 C条记忆,每条记忆形如一个三元组 (a,b,x),含义是第 b 次挤奶在第a 次挤奶结束至少 x天后进行。

现在请你帮 Bessie 算出在满足所有条件的前提下,每次挤奶的最早日期。

保证 Bessie 的记忆没有错误,这意味着一定存在一种合法的方案,使得:

第 i 次挤奶不早于第 Si天进行,且不晚于第 M 天进行;所有的记忆都得到满足;

输入格式

第一行三个整数 N,M,C。保证 1≤N,C≤105,92≤M≤109。

接下来一行包含 N 个整数 S1,S2,…,Sn,

下面 C行每行三个整数 a,b,x,描述一条记忆。
输出格式

输出 N行,每行一个整数,第 i 行的数表示第 i 次挤奶的最早日期。
输入输出样例
输入

4 10 3
1 2 3 4
1 2 5
2 4 2
3 4 4

输出

1
6
3
8

思路
在这里插入图片描述
题解:

#include <bits/stdc++.h>

using namespace std;

struct node {
	int val, next, len;
} e[1000086];

int n, m, c;
int cnt;
int head[1000086];
int dist[1000086];
int sum[1000086];
int vis[1000086];
int S[1000086];
const int inf = 0x3f3f3f3f;

void AddEdge (int u, int v, int w) {
	e[++cnt].val = v;
    e[cnt].next = head[u];
    e[cnt].len = w;
    head[u] = cnt;
}

bool SPFA () {
	queue <int> q;
	int s = 0;
	for (int i = 1; i <= n; i++)
		dist[i] = -inf;
	dist[s] = 0;
	sum[s] = 1;
	vis[s]++;
	q.push(s);
	while (!q.empty()) {
		int cur = q.front();
		q.pop();
		sum[cur] = 0;
		for (int p = head[cur]; p > 0; p = e[p].next)
			if (dist[e[p].val] < dist[cur] + e[p].len) {
				dist[e[p].val] = dist[cur] + e[p].len;
				vis[e[p].val]++;
				if (vis[e[p].val] >= n + 1)
					return true;
				if (!sum[e[p].val]) {
					q.push(e[p].val);
					sum[e[p].val] = 1;
				}
			}
	}
	return false;
}

int main () {
	scanf("%d%d%d", &n, &m, &c);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &S[i]);
		AddEdge(0, i, S[i]);
	}
	for (int i = 1, u, v, w; i <= c; i++) {
		scanf("%d%d%d", &u, &v, &w);
		AddEdge(u, v, w);
	}
	bool hahaha = SPFA();
	for (int i = 1; i <= n; i++)
		printf("%d\n", dist[i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值