差分约束
差分约束
(1)求不等式组的可行解
原点需要满足的条件:从原点出发,一定可以走到所有的边。
(这一点必须要满足,否则会遗漏某些不等式)
步骤:
- 先将每个不等式Xi <= Xj + Ck,转化成一条从Xj走到Xi,长度为Ck的一条边。
- 找一个超级原点,保证这个原点一定可以遍历所有边。
- 从原点求一遍单源最短路:
1 .如果存在负环,则原不等式组一定无解。
2 .如果不存在负环,则dist[i]就是原不等式组的一个可行解。
根据不等号的方向,建图时注意方向。
(2)如何求最大值或者最小值(这里的最值指的是每个变量的最值)
结论:如果要求最小值,则应该求最长路;如果要求最大值,则应该求最短路。
问题:如何转化Xi <= C,其中C是一个常数,这样的不等式?
方法:建立一个超级原点0,然后建立0->i的边,长度为C。
以求Xi的最大值为例:求所有从Xi出发,构成的不等式链Xi <= Xj + C1 <= Xk + C1 + C2 <= ... <= C1 + C2 + C3 +...(右边不能含有变量,否则无法求出最值)所计算出的上界,最终Xi的最大值等于所有上界的最小值(这里就可以得出求最大值用最短路的结论)。
下面根据一道例题来模拟一下上述过程:
Acwing1169.糖果
幼儿园里有 N 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。
但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的 K 个要求。
幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式
输入的第一行是两个整数 N,K。
接下来 K 行,表示分配糖果时需要满足的关系,每行 3 个数字 X,A,B。
如果 X=1.表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多。
如果 X=2,表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果。
如果 X=3,表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果。
如果 X=4,表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果。
如果 X=5,表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果。
小朋友编号从 1 到 N。
输出格式
输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1。
数据范围
yxc老师的分析过程:
根据题意我们可以判断出这是一道差分约束的题目,题目给了我们五个条件,并要求最小值,根据上述讨论我们可以根据这些不等式来建图,然后找一个超级原点,因为题干中说每个小朋友都可以都要分到糖果,所以我们可以假设一个超级原点0,从这点向其他所有点连一条边,长度为1(即代表每个小朋友都可以分到糖果),这同时保证了超级原点可以走到所有边,因为要求最小值,所以我们走一遍最长路即可,图中有正环的情况代表无解。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5+100, M = 3 * N;
int n,m;
LL dist[N];
int q[N],cnt[N];
int h[N],e[M],ne[M],w[M],idx;
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()
{
memset(dist,-0x3f,sizeof dist);//求最长路,初始化成-∞
dist[0] = 0; //超级原点
int hh = 0,tt = 0;
q[tt++] = 0;
st[0] = true;
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])
{
q[tt++] = j;
st[j] = true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
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 add(a,b,0);
}
//题目要求每个小朋友都会分到糖果,设定一个超级原点,向每个点都连一条边
//超级原点可以到达所有边,这也保证了它可以到达所有边,满足超级原点的要求
for(int i = 1; i<=n; i++) add(0,i,1);
if(spfa()) puts("-1"); //如果存在正环,则无解
else
{
LL ans = 0;
for(int i = 1; i<=n; i++) ans += dist[i];
printf("%lld\n",ans);
}
return 0;
}