一、前言
在介绍差分约束之前,我们首先需要知道差分约束是用来解决什么问题的:差分约束是一个用来解决形如 X<=Y+c
的二元不等式组的可行解的一个算法。在高中数学中我们会学习如何使用线性规划的方法来解决这个问题来求出不等式的可行解或者最大最小解。而在ACM中,差分约束算法可以将求解不等式组的问题转化为图论问题,通过图论中的知识来解决。
二、差分约束
1.求不等式组可行解的前提条件
- 将每一个未知数转化为图论上的一个点,根据不等式组建立一个有向图(建立有向图的具体方法将在后文中提及)
- 建立一个虚拟超级原点,从该原点出发可以到达有向图中的每一条边,一般建立一个原点,该点到每一个点都有一条长度为0的有向边。
- 对于每一个未知数X,从原点出发到X的距离就是X的一个满足条件的可行解。
2.算法步骤
- 先根据原不等式组建立有向图(建图的方法将在后文提及)
- 建立一个超级源点
- 使用SPFA算法从源点求单源最短(长)路
- 结果1:有向图存在负环,不等式无解
- 结果2:没有负环,dis[i] 就是原不等式组的一个可行解
注意:
1.图中存在点在求最短(最长)路后的距离仍然为INF(-INF),代表该未知数的取值不受任何约束,可以取任意值。
2.在实际解题时一定要找准找全题目中所有的不等式约束关系
3.建立有向图
1)求最大可行解
- 最大可行解:所有的可行解中某个未知数X的值最大
- 未知数X的最大可行解为:从超级源点到点X的最短路dis[x]
- 有向图建立方法:
- 将所有的不等式转化为
X <= Y + c
的形式 - 建立有向边
Y-->X
,长度为c - 注意一定是
<=
,如果是X < Y
可以转化为X <= Y - 1
- 如果存在不等式形如
c <= Y
,转化为X0 <= Y - c
,X0为超级源点。
- 将所有的不等式转化为
2)求最小可行解
- 最小可行解:所有的可行解中某个未知数X的值最小
- 未知数X的最小可行解为:从超级源点到点X的最长路dis[x]
- 有向图建立方法:
- 将所有的不等式转化为
X + c <= Y
的形式 - 建立有向边
X-->Y
,长度为c - 注意一定是
<=
,如果是X < Y
可以转化为X + 1 <= Y
- 如果存在不等式形如
Y <= c
,转化为Y - c <= X0
,X0为超级源点。
- 将所有的不等式转化为
3)证明
- 以求xi的最大值为例:求从xi出发,构成的不等式链
xi <= xj + c1 <= xk + c1 +c2 <= ...... <= c1+c2+c3+...+cn
,求出来的上界的最小值就是可行解的最大值。
代码(结合例题)
糖果
幼儿园里有 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。
数据范围
1≤N<1e5,
1≤K≤1e5,
1≤X≤5,
1≤A,B≤N
输入样例:
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
输出样例:
11
正确代码
// Problem: 糖果
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/1171/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define endl '\n'
#define fi first
#define se second
#define pb push_back
#define int long long
#define INF 0x3f3f3f3f
#define ull unsigned long long
#define print(x) cout << (x) << endl
#define all(x) (x).begin(), (x).end()
#define mem(a, b) memset(a, b, sizeof(a))
#define f(i, l, r) for (int i = (l); i <= (r); i++)
#define ff(i, l, r) for (int i = (l); i >= (r); i--)
#define pr(x, n) f(_, 1, n) cout << (x[_]) << " \n"[_ == n];
#define ck(x) cerr << #x << "=" << x << '\n';
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 7, mod = 1e9 + 7;
int n, k;
int h[N], e[N], ne[N], v[N], id;
int dis[N];
int cnt[N];
bool in_st[N];
void add(int a, int b, int c)
{
ne[id] = h[a], h[a] = id, e[id] = b, v[id++] = c;
}
bool spfa()
{
mem(dis, -0x3f);
stack<int> st;
dis[0] = 0;
st.push(0);
in_st[0] = true;
while (st.size())
{
int te = st.top();
st.pop();
in_st[te] = false;
for (int i = h[te]; ~i; i = ne[i])
{
int j = e[i];
if (dis[j] < dis[te] + v[i])
{
dis[j] = dis[te] + v[i];
cnt[j] = cnt[te] + 1;
if (cnt[j] > n)
{
return false;
}
if (!in_st[j])
{
st.push(j);
in_st[j] = true;
}
}
}
}
return true;
}
void solve()
{
mem(h, -1);
cin >> n >> k;
f(i, 1, n)
{
add(0, i, 1);
}
while (k--)
{
int a, b, op;
cin >> op >> a >> b;
if (op == 1)
add(a, b, 0), add(b, a, 0);
else if (op == 2)
add(a, b, 1);
else if (op == 3)
add(b, a, 0);
else if (op == 4)
add(b, a, 1);
else
add(a, b, 0);
}
if (spfa())
{
int ans = 0;
f(i, 1, n) ans += dis[i];
print(ans);
}
else
print(-1);
}
signed main()
{
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
clock_t start_time = clock();
int __ = 1;
// cin>>__;
// init();
while (__--)
solve();
clock_t end_time = clock();
// cerr << "Running time is: " << ( double )(end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
return 0;
}
作者:Avalon·Demerzel
更多文章详见专栏:图论与数据结构