差分约束问题
一.用处 :差分约束是用来解决经一系列不等式限制后求可行解或最大值最小值的问题
二.解法:
-
审题,列出不等式关系
-
根据题意,判断求最短路还是最长路(一般求最小值就是用最长路,求最大值就是最短路,如果不理解原理,记住就好)
-
建边:当求最短路时,将所有的不等式化成 a ≥ b + c a≥b+c a≥b+c,求最长路时,化为 a ≤ b + c a≤b+c a≤b+c的形式(当不等关系不存在等于号时,可将等号的一边加一,化为 ≥ ≥ ≥或 ≤ ≤ ≤,例如: a < b a<b a<b,则可化为 a + 1 ≤ b a+1≤b a+1≤b)
-
特判:当不等式组无解时,有两种情况:
(1).若使用 S P F A SPFA SPFA求最长路,则存在正环,使用判正环的模板即可,若求最短路,则存在负环,那么使用判负环的代码。
(2).起点与终点不连通,那么最后的 d i s t [ n ] dist[n] dist[n]将会是我们刚开始定义的无穷大
三.例题:
此题出自 SCOI2011
题目描述
幼儿园里有 N N N 个小朋友, lxhgww \text{lxhgww} lxhgww 老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, lxhgww \text{lxhgww} lxhgww 需要满足小朋友们的 K K K个要求。幼儿园的糖果总是有限的, lxhgww \text{lxhgww} lxhgww 想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式
输入的第一行是两个整数 N , K N,K N,K。接下来 K K K 行,表示这些点需要满足的关系,每行 3 3 3 个数字, X , A , B X,A,B X,A,B。
如果
X
=
1
X=1
X=1, 表示第
A
A
A 个小朋友分到的糖果必须和第
B
B
B 个小朋友分到的糖果一样多;
如果
X
=
2
X=2
X=2, 表示第
A
A
A个小朋友分到的糖果必须少于第
B
B
B 个小朋友分到的糖果;
如果
X
=
3
X=3
X=3, 表示第
A
A
A 个小朋友分到的糖果必须不少于第
B
B
B 个小朋友分到的糖果;
如果
X
=
4
X=4
X=4, 表示第
A
A
A 个小朋友分到的糖果必须多于第
B
B
B 个小朋友分到的糖果;
如果
X
=
5
X=5
X=5, 表示第
A
A
A 个小朋友分到的糖果必须不多于第
B
B
B 个小朋友分到的糖果;
输出格式
输出一行,表示 lxhgww \text{lxhgww} lxhgww 老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 − 1 -1 −1。
样例输入 :
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
样例输出:
11
对于此题
求的是最小值,所以使用最长路,所以需要构造 ≥ ≥ ≥
当 x = 1 x=1 x=1时,两个小朋友的糖果一样多,但是建边时都是不等号,所以构造 a ≥ b + 0 , b ≥ a + 0 a≥b+0,b≥a+0 a≥b+0,b≥a+0,就可以建一条从 a a a到 b b b,边长为 0 0 0的边,再建一条从 b b b到 a a a,长度为 0 0 0的边。
当 x = 2 x=2 x=2时,第 a a a个小朋友分到的糖果必须少于第 b b b 个小朋友分到的糖果,所以得到不等式 a < b a<b a<b,但是需要的是小于等于,转化后变成 b ≥ a + 1 b≥a+1 b≥a+1;就可以建一条从 a a a到 b b b,长度为 1 1 1的边。
当 x = 3 x=3 x=3时,第 a a a 个小朋友分到的糖果必须不少于第 b b b 个小朋友分到的糖果;得到不等式a≥b+0,所以建一条从b到a,长度为0的边。
当 x = 4 x=4 x=4时,第 a a a 个小朋友分到的糖果必须多于第 b b b 个小朋友分到的糖果;得到 a > b a>b a>b,转化后得 a ≥ b + 1 a≥b+1 a≥b+1,建 b b b到 a a a,长度为 1 1 1的边。
当 x = 5 x=5 x=5时,表示第 a a a 个小朋友分到的糖果必须不多于第 b b b 个小朋友分到的糖果;得到 b ≥ a + 0 b≥a+0 b≥a+0,建 a a a到 b b b,长度为 0 0 0的边
此外,每个小孩子得到的糖果都是大于等于 0 0 0的,所以我们还需要建一个超级源点,给每个点连一条长度为 0 0 0的边,最后以超级源点作为起点即可。
此题无解的情况为存在正环,例如
b
b
b的糖果需要比
a
a
a多,
c
c
c的糖果需要比
b
b
b多,
a
a
a的糖果需要比
c
c
c多,那么就出现了正环,此时,在
S
P
F
A
SPFA
SPFA中这三个点会无限进队列,因此我们只需要记录下他们进队列的次数,当进队列次数大于总的人数时,就一定出现了正环。则输出
−
1
-1
−1。
(此题无需考虑到不了某个小孩的那种情况,因为如果那个小孩和其他小孩都没关系,因为球的时所需糖的最小数,那么直接不给这个小孩糖,因此对此题无影响)
最后附上代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100010 , M = 3*N;
int h[N], w[M], ne[M], e[M], idx, res;
int n,m;
int dist[N];
int 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 ++;
}
//spfa模板
bool spfa(){
memset(dist,-0x3f,sizeof dist); //初始化dist为负无穷
dist[0]=0; //超级源点入栈
stack<int> q; //有时queue会TLE,可以尝试用stack
q.push(0);
st[0]=true;
while(q.size()){
int t=q.top();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;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; //如果进栈次数大于n,则存在正环
if(!st[j]){
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
signed main(){
cin >> n >> m;
memset(h,-1,sizeof h);
while(m -- ){
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);
}
for(int i = 1; i <= n; i ++ ){
add(0, i, 1);
}
if(spfa()) cout << -1 << endl; //如果存在正环,输出-1
else{ //否则,给每个小孩的糖果数加起来
for(int i = 1; i <= n; i ++){
res += dist[i];
}
cout<<res;
}
return 0;
}
第一次写博客,如有错误之处望指出