差分约束系统是线性规划问题的一个特例。k行n列的线性规划矩阵
A
的每一行包含一个1和一个-1,其余均为0。也就是说,每个约束条件都形如
单源最短路问题
这个问题可以从另一个角度来理解:给每个顶点
v
分配一个顶标
1.
δ(s,v)≤δ(s,u)+w(u,v),(u,v)∈E
2.
存在(u,v)∈E,使得δ(s,v)=δ(s,u)+w(u,v)
约束图
移项得
发现它和最短路问题中的三角不等式是一致的:
由此,我们建图如下,得到差分约束系统的一组可行解或判其无解:
添加额外的源点
v0
,从
v0
向每个
xi
连一条有向边,边权为0。对于每个不等式
xj−xi≤bk
,连有向边
(vi,vj)
,
w(vi,vj)=bk
。求以
v0
为源点的单源最短路,如果存在负圈,则无解;否则,
xi=δ(s,vi),1≤i≤n
是差分约束系统的一组可行解。
这样的一个图称为约束图。
无解
为什么存在负圈意味着差分约束系统无解呢?我们来证明一下。
设
<v1,v2,...,vm>
<script id="MathJax-Element-17" type="math/tex">
</script>是约束图中的一个负环,
v1=vm
。相应地,差分约束系统中有不等式
全部加起来,有
这是不科学的。证毕。
转化
解的特性
定理1:如果
(x1,x2,...,xn)
是一组可行解,那么
(x1+d,x2+d,...,xn+d)
也是一组可行解。
证明略。
定理2:由约束图得到的可行解满足
xi≤0
。
证明略。
定理3:由约束图得到的可行解与其他满足
xi≤0
的可行解相比,最大化
Σni=1xi
。
假设另有一组满足
yi≤0
的可行解
(y0,y1,y2,...,yn)
,为了统一起见,同样附加一个
y0=0
。考察源点到任意一点,不妨设其为
vn
,最短路径为
<v0,v1,v2,...,vn>
<script id="MathJax-Element-4537" type="math/tex">
</script>。
x0≥y0
。假设
xi≥yi
成立,那么
xi+1=xi+w(vi,vi+1)≥yi+w(vi,vi+1)≥yi+1
。第一个等号是根据最短路的性质。由数学归纳法,对于所有
xi
,均有
xi≥yi
成立,因而最大化了
Σni=1xi
。
UPDATE 2016.9.10
和WZH学长讨论有没有更直观的证法,受到启发,如下:
x、y还是和上面一样。从
v0
到
vn
,有
和
分别全部相加,得
代入 x0=y0=0 ,得
证毕。
定理4:由约束图得到的可行解最小化
max{xi}−min{xi}
。
由定理1,我们可以“平移”解向量。这个变换不改变各个变量的相对大小。假设另有一组可行解
(y1,y2,...,yn)
,将它平移,使得
max{yi}=max{xi}
。由定理3的证明,
min{xi}≥min{yi}
,故
max{xi}−min{xi}≤max{yi}−min{yi}
。
如果我们要最小化 Σni=1xi ,最大化 max{xi}−min{xi} ,该怎么做呢?
把最短路改为最长路即可。
怎么求最长路呢?一种方法是把所有边权取反,用SPFA求最短路。另一种方法是直接修改SPFA松弛的条件。注意这里的最长路与无权简单最长路的区别,所以它不是NP完全问题。
例题
Bzoj 2330 [SCOI2011]糖果
一开始TLE,上网搜索,得知这道题有两个坑:
1. 有一组数据是一条很长的链。不知道除了面向数据还有什么解决方法呢?
2. 有一组数据存在很大的负环。虽然这组数据存在负的自环,可以直接判掉……
关于负环的判定,我参考了lydrainbowcat的方法:记录最短路径的长度。
不良心的出题人。
#include <cstdio>
#include <queue>
#include <cctype>
#define NO_SOL() {puts("-1"); return 0;}
using namespace std;
typedef long long ll;
const int MAX_N = 100000, MAX_K = 100000;
int e_ptr = 1, n, k, head[MAX_N+1], d[MAX_N+1];
ll dis[MAX_N+1];
bool inq[MAX_N+1];
struct Edge {
int v, next;
ll w;
} E[MAX_K*2+1];
inline void add(int u, int v, ll w)
{
E[e_ptr] = (Edge){v, head[u], w};
head[u] = e_ptr++;
}
bool SPFA()
{
queue<int> Q;
for (int i = 1; i <= n; ++i) {
inq[i] = true;
dis[i] = -1;
Q.push(i);
}
int u;
while (!Q.empty()) {
u = Q.front();
Q.pop();
inq[u] = false;
for (int i = head[u]; i; i = E[i].next) {
int v = E[i].v;
ll upd = dis[u] + E[i].w;
if (dis[v] > upd) {
dis[v] = upd;
d[v] = d[u] + 1;
if (d[v] > n)
return false;
if (!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return true;
}
template<typename T>
inline void read(T& x)
{
x = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c)) {
x = x*10 + c - '0';
c = getchar();
}
}
int main()
{
read(n);
read(k);
int x, a, b;
for (int i = 0; i < k; ++i) {
read(x);
read(a);
read(b);
switch (x) {
case 1: // a = b
add(a, b, 0);
add(b, a, 0);
break;
case 2: // a < b
if (a == b)
NO_SOL();
add(a, b, -1);
break;
case 3: // a >= b
add(b, a, 0);
break;
case 4: // a > b
if (a == b)
NO_SOL();
add(b, a, -1);
break;
case 5: // a <= b
add(a, b, 0);
}
}
// 求最长路,边权取负后求最短路
if (!SPFA())
NO_SOL();
ll ans = 0;
for (int i = 1; i <= n; ++i)
ans -= dis[i];
printf("%lld\n", ans);
return 0;
}