1.总结
注意求最大值是求的最小上界(因为不等式是a<b+w,b->a),最小值求的是最大下界 (因为不等式是a>b+w)
差分约束两大应用
应用一:
1 求不等式组的可行解
⭐源点需要满足的条件: 从源点出发,一定可以走到所有的边
否则 用单源最短路做的话 有一条边走不到 则该边对应的不等式就无法满足
某一个点x[i]走不到无所谓(某个点走不到代表它不受限制,x[i]取任意数都可以)
过程:
1 把每个x[i] ≤ x[j] + C[k]不等式转化为一条从x[j]走到x[i]长度为C[k]的边
2 然后在这个图上找一个超级源点,使得该源点一定可以遍历到所有边
3 从源点求一遍 单源最短路
3.1 假如存在负环
x[1]→x[2]→x[3]→x[k]
↑ c1 c2 c3 ↓
← ← ← ← ← ←
ck
x[2]≤ x[1]+c[1]
...
x[k]≤ x[k-1]+c[k-1]
x[1]≤ x[k]+c[k]
对第一个不等式用后面的不等式一直做松弛
x[2] ≤ x[1]+c[1]
≤ x[k]+c[k]+c[1]
≤ x[k-1]+c[k-1]+c[k]+c[1]
...
≤ x[2]+c[2]+...+c[k-1]+c[k]+c[1]
≤ x[2]+(小于零的Σc[i])
x[2] < x[2]
即矛盾
得出结论:不等式无解 <=> 存在负环
4 求完单源最短路之后
4.1 存在负环 => 不等式无解
4.2 没有负环 => 求完之后一定是满足这个不等式的 <=> 即一个可行解
x[i] ≤ x[j] + C[k]
x1 ≤ x2+1
{ x2 ≤ x3+2
x3 ≤ x1-5
x1 = 0
x2 = -1
x3 = -2
类比最短路
i→j 求之前 d[j] > d[i]+c
c 求完后 d[j] ≤ d[i]+c
一个图里每个点求完最短距离后每个点的最短距离都有第二个不等式满足
即 任何一个最短路问题 可以 转化为一个差分约束问题
同理 一个差分约束问题 可以 转化为一个单源最短路问题
最长路
i→j 求之前 d[j] < d[i]+c
c 求完后 d[j] ≥ d[i]+c
应用二:
2 如何求最大值或者最小值(x[i] for i in range(1,n))
结论1:如果求的是最小值,则应该求最长路,如果求的是最大值,则应该求最短路
问题1:如何转化x[i] ≤ c 其中c是一个常数 这类的不等式
方法:建立一个超级源点,0号点x[0],然后建立0→i 长度是c的边即可
x[i] ≤ c
<=>
x[i] ≤ x[0] + c = 0 + c
以求x[i]的最大值为例:所有从x[i]出发,构成的不等式链
x[i] ≤ x[j] + c[j]
≤ x[k] + c[k] + c[j]
≤ x[0] + c[1]+ c[2]+... + c[j]
= 0 + c[1]+ ... + c[j]
所计算出的上界,
最终x[i]的最大值
=所有上界的最小值
举例 x[i] ≤ 5
x[i] ≤ 2
x[i] ≤ 3
max(x[i]) = min(5,2,3) = 2
0 → 1 → 3 → 5 → ... → i
c1 c3 c5 ci-1
x[1] ≤ x[0] + c[1]
x[3] ≤ x[1] + c[3]
x[5] ≤ x[3] + c[5]
...
x[i] ≤ x[i-1] + c[i-1]
则
x[i] ≤ x[i-1] + c[i]
≤ x[i-3] + c[i-3] + c[i]
...
≤ x[0] + c[1] + c[3] + c[i-3] + c[i-1]
⭐可以发现Σc[i]就是从0→i的一条路径的长度
那么
求x[i]最大值
<=>
求所有上界的最小值
<=>
求所有从0→i的路径和的最小值
<=>
最短路求dist[i]
同理 求x[i]最小值
<=>
求所有下界的最大值
<=>
求所有从0→i的路径和的最大值
<=>
最长路求dist[i]
作者:仅存老实人
链接:https://www.acwing.com/solution/content/20514/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2.分配问题
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, M = 3 * N;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N], q[N];
bool st[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool spfa() {
//因为是求所有x_i的最小值,因此就是求不等式的下界的最大值
//转而就是求图论的最长路
int hh = 0, tt = 1;
memset(dist, -0x3f, sizeof dist);
dist[0] = 0;
q[0] = 0;
//建立一个能够到所有点的虚拟源点0
for (int i = 1; i <= n; ++i) add(0, i, 1); //x_0 <= x_i + 1
//这题在判负环的时候会TLE
//上一次的“负环”题目中的trick方法还是太玄学了
//这里用一个不会TLE的判负环方法,那就是把SPFA算法中的循环队列改为栈
//这样对于遇到的负环,就不会加入队尾,知道再次遍历完整个队列才去算他
//遇到负环会直接在栈顶连续入栈出栈,直到判断他的cnt[i] >= n+1,即发现负环
while (hh != tt) {
int t = q[--tt];
st[t] = false;
for(int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] < dist[t] + w[i]) {
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n + 1) return true;
if (!st[j]) {
st[j] = true;
q[tt++] = j;
}
}
}
}
return false;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
while (m--) {
int x, a, b;
scanf("%d%d%d", &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 if (x == 5) add(a, b, 0);
}
if (spfa()) puts("-1");
else {
LL res = 0;
for (int i = 1; i <= n; ++i) res += dist[i];
printf("%lld\n", res);
}
return 0;
}
作者:一只野生彩色铅笔
链接:https://www.acwing.com/solution/content/37324/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.前缀和解区间
#include <iostream>
#include <cstring>
using namespace std;
const int N = 50010, M = 150010;
int n;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int q[N];
bool st[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void spfa() {
memset(dist, -0x3f, sizeof dist);
int hh = 0, tt = 0;
q[tt++] = 0;
st[0] = true;
dist[0] = 0;
while (hh != tt) {
int t = q[hh++];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] < dist[t] + w[i]) {
dist[j] = dist[t] + w[i];
if (!st[j]) {
q[tt++] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
}
int main() {
scanf("%d", &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;
scanf("%d%d%d", &a, &b, &c);
a++, b++;
add(a - 1, b, c);
}
spfa();
printf("%d\n", dist[50001]);
return 0;
}
作者:你好世界wxx
链接:https://www.acwing.com/solution/content/42920/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
4.排队布局
/*
x[i] 表示 第i头牛位置
以求x[i]的最大值为例:所有从x[i]出发,构成的不等式链
x[i] ≤ x[j] + c[j]
≤ x[k] + c[k] + c[j]
≤ x[0] + c[1]+ c[2]+... + c[j]
= 0 + c[1]+ ... + c[j]
所计算出的上界,
最终x[i]的最大值
=所有上界的最小值
举例 x[i] ≤ 5
x[i] ≤ 2
x[i] ≤ 3
max(x[i]) = min(5,2,3) = 2
0 → 1 → 3 → 5 → ... → i
c1 c3 c5 ci-1
x[1] ≤ x[0] + c[1]
x[3] ≤ x[1] + c[3]
x[5] ≤ x[3] + c[5]
...
x[i] ≤ x[i-1] + c[i-1]
则
x[i] ≤ x[i-1] + c[i]
≤ x[i-3] + c[i-3] + c[i]
...
≤ x[0] + c[1] + c[3] + c[i-3] + c[i-1]
⭐可以发现Σc[i]就是从0→i的一条路径的长度
最大位置→不等式上界(小于等于)→上界里的最小值→最短路
x[i] ≤ x[j] + c j → i
1 每头奶牛按编号排序 i+1 → i w = 0
<=> x[i] ≤ x[i+1]
2 两者之间的距离不超过一个给定的数L
x[b]-x[a] ≤ L a → b w = L
x[b] ≤ x[a] + L
3 两者之间的距离不小于一个给定的数D
x[b]-x[a] ≥ D
x[a] ≤ x[b]-D b → a w = -D
问题1:
因为没有一个点可以无条件到所有点,
所以建超级源点0 从0向
假定所有x[i] ≤ x[0] + 0 从而可以从0向x[i]连一条长度为0的边
1 如果没有负环-有解
2 如果有负环-无解
问题2:
直接把所有点i加入队列 == 创建超级源点0
点1和点n距离是否可以无限大?
则可以把点1固定在一个位置上(选0位置)x[1] = 0,判断x[n]是否可以无限大
即求从1→n的最短路径,由于x[1]取0,则x[n]代表了n和1的最大距离,如果x[n]<INF,说明d[1→n]有限大≤x[n]
所以看x[n]是否是正无穷就可以判断x[n]是否可以无限大
问题3:
链式法则 x[i]最终会小于一个常数c
x[n]-x[1] ≤ ... ≤ c
每一个上界c都对应一条 起点x[1]=0到i的路径
求1与n的距离最大值 == 求所有上界c的最小值 == 求1~n所有路径长度的最小值
*/
作者:仅存老实人
链接:https://www.acwing.com/solution/content/24416/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
//第一种边n条<=1000 第2、3种边各10000条
const int N = 1010, M = 21010, INF = 0x3f3f3f3f;
int n, m1, m2;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int q[N], cnt[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
bool spfa(int size)
{
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
queue<int> q;
for(int i=1;i<=size;i++)
{
q.push(i);
dist[i] = 0;
st[i] = true;
}
while(q.size())
{
int t=q.front();
q.pop();
st[t] = false;
for(int i = h[t];~i;i=ne[i])
{
int j = e[i];
if(dist[j]>dist[t]+w[i])//最短路
{
dist[j] = dist[t]+w[i];
cnt[j] = cnt[t]+1;//这条路上点的个数
if(cnt[j]>=n)return true;//负环--无解
if(!st[j])//j不在队列中
{
q.push(j);//j加入队列
st[j] = true;//标记j
}
}
}
}
return false;
}
int main()
{
cin >> n >> m1 >> m2;
memset(h,-1,sizeof h);
for(int i=1;i<n;i++) add(i+1,i,0);//x[i] ≤ x[i+1]+0
while(m1--)
{
int a,b,c;
cin >> a >> b >> c;
if(a>b) swap(a,b);//x[b] ≤ x[a] + L a→b
add(a,b,c);
}
while(m2--)
{
int a,b,c;
cin >> a >> b >> c;//x[a] ≤ x[b]-D b→a
if(a>b) swap(a,b);
add(b,a,-c);
}
// 第一问 问这些约束有没有解 从超级源点出发==把所有点加入队列 看有没有负环
if(spfa(n)) cout << -1;
//如果不存在满足要求的方案,输出-1;
// 第二问 只需要把第一个点加入队列 求第1个点到第n个点的最短距离
else
{
spfa(1);
//如果 1 号奶牛和 N 号奶牛间的距离可以任意大,输出-2;
if(dist[n]==INF) cout << -2;
//否则,输出在满足所有要求的情况下,1 号奶牛和 N 号奶牛间可能的最大距离。
else cout << dist[n];
}
return 0;
}
作者:仅存老实人
链接:https://www.acwing.com/solution/content/24416/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
5.雇佣收银员
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 30, M = 100, INF = 0x3f3f3f3f;
int n;
int h[N], e[M], w[M], ne[M], idx;
int r[N], num[N];
int dist[N];
int q[N], cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void build(int c)
{
memset(h, -1, sizeof h);
idx = 0;
add(0, 24, c), add(24, 0, -c);
for (int i = 1; i <= 7; i ++ ) add(i + 16, i, r[i] - c);
for (int i = 8; i <= 24; i ++ ) add(i - 8, i, r[i]);
for (int i = 1; i <= 24; i ++ )
{
add(i, i - 1, -num[i]);
add(i - 1, i, 0);
}
}
bool spfa(int c)
{
build(c);
memset(dist, -0x3f, sizeof dist);
memset(cnt, 0, sizeof cnt);
memset(st, 0, sizeof st);
int hh = 0, tt = 1;
dist[0] = 0;
q[0] = 0;
st[0] = true;
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] < dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= 25) return false;
if (!st[j])
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return true;
}
int main()
{
int T;
cin >> T;
while (T -- )
{
for (int i = 1; i <= 24; i ++ ) cin >> r[i];
cin >> n;
memset(num, 0, sizeof num);
for (int i = 0; i < n; i ++ )
{
int t;
cin >> t;
num[t + 1] ++ ;
}
bool success = false;
for (int i = 0; i <= 1000; i ++ )
if (spfa(i))
{
cout << i << endl;
success = true;
break;
}
if (!success) puts("No Solution");
}
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/153547/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。