差分约束
差分约束问题是求形如\(x_i \leq x_j + c_k\)的一组不等式的解,且求解的方式是转化为图论中的求单源最短路。
转化为求单源最短路
- 不等式 \(x_i \leq x_j + c_k\) 和图中一条从\(x_i\)到\(x_j\)长度为\(c_k\)的边相对应;
- 若在图中求单源最短路,则有
dist[i] <= dist[j] + k
; - 因此,可以将求不等式组的可行解转换成在对应图中求单源最短路
Part1.求不等式组的可行解
- 源点需要满足的条件:从源点出发,一定可以走到所有的边(否则所求结果并未满足全部的约束条件)
步骤
- 先将每一个不等式\(x_i \leq x_j + c_k\),转化成一条从\(x_j\)走到\(x_i\),长度为\(c_k\)的一条边
- 找一个超级源点,使得该源点一定可以遍历所有边
- 从源点求一遍单源最短路
负环
如果存在负环,则原不等式组一定无解,反之dist
数组会存放原不等式组的一组解
证明:
x1 —— x2 —— x3
| |
xk ——...—— x4
假设\(x_1,x_2,x_3,...,x_k\)构成负环,则可推知 \(x_1 \leq x_k+c_k \leq x_{k-1}+c_{k-1}+c_k \leq...\leq x_1+c_1+...+c_k\);因为是负环,所以\(c_1+...+c_k\leq0\),所以存在负环一定无解;反之,不等式组无解,对应图中存在负环。
Part2.如何求最大值或者最小值(每一个变量\(x_i\)的最值)
此类差分约束问题的特点
\(x_i \leq x_j + c_k\) 表示\(x_i\) 与 \(x_j\)存在相对关系,\(x_i \leq c\)表示\(x_i\)的绝对关系;如果不等式组表述的都是相对关系,则不等式组要么无解,如果有解则必有无穷多解;所以在要求最大值/最小值时,题目必然会给出一个绝对条件
绝对条件\(x_i \leq c\)的处理方式
建立一个超级源点\(0\) ,建立一个\(0\)指向\(x_i\)且长度为\(c\)的边
结论
如果求最小值,则应该求最长路;如果求的是最大值,则应该求最短路
证明:
以求\(x_i\)的最大值为例:
x0 —— x1 —— x2 ——...—— xi
- 首先,从源点\(0\)到\(x_i\)的一条路径(如\(x_0,x_1,x_3,...,x_{i-1},x_i\))可表述为不等式链\(x_i \leq x_{i-1}+c_{i-1} \leq x_{i-2}+c_{i-1}+c_{i-2} \leq x_1+c_1+...+c_{i-1}\leq c_0+c_1+...+c_{i-1}\)
- 即\(x_i \leq c_0+c_1+...+c_{i-1}\),也就是这条路径的长度
- 且需要满足所有的不等式链(路径),则应该求出所有路径中的最小值(\(x_i\)应该小于等于所有路径长度中的最小值),也即应该求最短路。最终求出的
dist[i]
就是\(x_i\)可以取到的最大值
例题-糖果
题解
- 题目要求最小值,则应该求最长路
- 根据题意,可得出不等式组:
//求最长路,则关系式应使用 >= 描述;例如A >= B+1,表示 dist[A] >= dist[B] + 1
x = 1 A >= B 且 B >= A
x = 2 B >= A + 1
x = 3 A >= B
x = 4 A >= B + 1
x = 5 B >= A
以及隐含条件“每个小朋友都能够分到糖果”:
假设一个超级源点X0,则任意节点满足: X >= X0 + 1
- 检测:从源点0出发可以走到所有的点,则必然能够走到所有边;源点0满足条件
- 使用spfa算法求最长路,同时检测是否存在正环。如果存在正环,表示无解;反之所有
dist[1~n]
的和即为糖果的最小值
代码
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 100010 , M = 300010;
int e[M] , ne[M] , w[M] , h[N] , idx;
LL dist[N];
int stack[N] , cnt[N];
bool st[N];
int n,m;
void add(int l, int r, int d)
{
e[idx] = r , w[idx] = d, ne[idx] = h[l], h[l] = idx++;
}
bool spfa()
{
memset(dist , -0x3f , sizeof dist);
int hh = 0, tt = 0;
stack[0] = 0;
dist[0] = 0;
while(hh <= tt)
{
int u = stack[tt --];
st[u] = false;
for(int i = h[u]; ~i; i = ne[i])
{
int v = e[i];
if(dist[v] < dist[u] + w[i])
{
dist[v] = dist[u] + w[i];
cnt[v] = cnt[u] + 1;
if(cnt[v] >= n + 1) return false;
if(!st[v]) st[v] = true , stack[++ tt] = v;
}
}
}
return true;
}
int main()
{
cin >> n >> m;
memset(h , -1 , sizeof h);
for(int i = 1; i <= n; i++) add(0 , i , 1);
for(int i = 0; i < m; i++)
{
int x , a , b;
cin >> x >> a >> b;
if(x == 1) add(a , b , 0) , add(b , a , 0);
else if(x == 2) add(a , b , 1);
else if(x == 3) add(b , a , 0);
else if(x == 4) add(b , a, 1);
else add(a , b , 0);
}
if(!spfa()) puts("-1");
else
{
LL ans = 0;
for(int i = 1; i <= n; i++)
ans += dist[i];
cout << ans << endl;
}
return 0;
}
注意点
一般来说,spfa算法使用队列,可以减少更新次数,从而减少运行时间;但是仅就检测负环问题,使用栈来代替队列会运行得更快,因为栈先进先出得性质能更快的在负环中不断迭代。本题中就使用了栈来代替队列,否则会TLE。
例题-区间
题解
- 题目要求最小值,则应该求最长路
- 对于任意一个区间
[a,b]
有Xa + Xa+1 + ... + Xb >= c (Xi=1表示选择了i,Xi=0表示没有选择)
- 显然利用前缀和可以将上式转化为符合差分约束关系的一般形式:
Sb - Sa-1 >= c
- 还应满足的约束条件是:
0 <= Xi <= 1 即 0 <= (Si - Si-1) <= 1
总结:
Sb >= Sa-1 + c
Si >= Si-1 + 0
Si-1 >= Si - 1
- 因为使用前缀和,所以需要将区间下标映射到
[1 , 50001]
,将0空出来;同时由Si >= Si-1 + 0
知,0可以作为源点,使得可以遍历所有的边
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 50010 , M = N * 3;
int e[M] , ne[M] , w[M] , h[N] , idx;
int dist[N] , q[N];
bool st[N];
int n;
void add(int l ,int r, int v)
{
e[idx] = r , w[idx] = v , ne[idx] = h[l] , h[l] = idx ++;
}
void spfa()
{
memset(dist , -0x3f , sizeof dist);
dist[0] = 0;
int hh = 0 , tt = 1;
q[0] = 0;
while(hh != tt)
{
int u = q[hh ++];
if(hh == N) hh = 0;
st[u] = false;
for(int i = h[u]; ~i; i = ne[i])
{
int v = e[i];
if(dist[v] < dist[u] + w[i])
{
dist[v] = dist[u] + w[i];
if(!st[v])
{
st[v] = true;
q[tt++] = v;
if(tt == N) tt = 0;
}
}
}
}
}
int main()
{
cin >> n;
memset(h , -1, sizeof h);
for(int i = 1; i <= 50001; i++) add(i-1 , i , 0) , add(i , i-1 , -1);
for(int i = 0; i < n; i++)
{
int a, b , c;
cin >> a >> b >> c;
add(a , b + 1, c);
}
spfa();
printf("%d\n" , dist[50001]);
return 0;
}
参考文献
Acwing-算法提高课-图论章节
https://www.acwing.com/activity/content/introduction/16/