1,定义
差分约束是由n个变量,m条不等式组成的n元一次不等式组,不等式的形式都是形如a-b<=c,要求判断是否有解
2,思路
差分约束可以用最短路或者最长路实现,两者思想与算法大同小异,后面会例题一会专门介绍。
我们发现,不等式形式与最短路的dis[v]<=dis[u]+w形式相似,如果我们对u-v<=c建立v->u的边,所有边建立后,只要不存在负环,最终一定会满足不等式组。
我们可以选一个点做起点,一般是自己起一个源点s,s向其他点连s->u(边权为0)的边,那么最终所有点到源点s的距离满足dis[u]<=s(dis[s]=0),每个点的dis[u]就是答案之一。
SPFA板子:最短路径三大算法——3,SPFA算法(复杂度O(n*m))
3,模板题:P5960 【模板】差分约束算法
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f; //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 5e3 + 10;
int n, m, dis[N], cnt[N], head[N], num;
bool vis[N];
struct node
{
int next, to, t;
} edge[N * N];
void add(int u, int v, int w)
{
edge[++num].next = head[u];
edge[num].to = v;
edge[num].t = w;
head[u] = num;
}
bool spfa()
{
memset(dis, 0x3f, sizeof(dis));
dis[0] = 0;
queue<int>q;
q.push(0);
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to, w = edge[i].t;
if (dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] == n + 1)return 0;
if (!vis[v])vis[v] = 1, q.push(v);
}
}
}
return 1;
}
int main()
{
cin >> n >> m;
int u, v, w;
for (int i = 1; i <= n; ++i)add(0, i, 0);
for (int i = 1; i <= m; ++i)
{
cin >> u >> v >> w;
add(v, u, w);
}
if (spfa())
{
for (int i = 1; i <= n; ++i)cout << dis[i] << ' ';
}
else cout << "NO" << endl;
}
4,例题一:Intervals
思路:
1,我们设s[i]表示1~i中使用了s[i]个数,则对于(a,b,c)有s[b]-s[a-1]>=c(注意是a-1,因为s[a]包括a的使用,s[b]-s[a-1]就是a~b的使用数量
2,我们也应注意到:0<=s[i]-s[i-1]<=1.
3,我们讨论一下最长路与最短路算法与思路的区别,区间[l,r]
最短路形式:左边<=右边。
1,会发现,我们每次更新左边,其实都是让左边“尽可能大”,因为他明明可以取小于右边的值中众多一个,但是偏偏取值==右边(最大),所以如果我们按照正常思路spfa从l跑到r,答案一定是s[r]==r(符合条件的答案,但是不是最小,而是最大(笑))。
2,所以用最短路解决最小值问题,聪明的你一定明白正难则反,从r跑到l,即,我们每次用r更新了l时,都是让s[l]尽可能的大从而接近s[r],最后得出的答案s[r]-s[l-1]就会尽可能小(即最小值),一般设s[r]=0,答案就是-s[l-1]。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f; //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 5e4 + 10;
int head[N *3], dis[N];
bool vis[N];
int num, l, r;
struct node
{
int next, to, w;
} edge[N *3];//最多建立N*3的边
void add(int u, int v, int w)
{
edge[++num].next = head[u];
edge[num].to = v;
edge[num].w = w;
head[u] = num;
}
void spfa()
{
queue<int>q;
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[r] = 0;//一般设dis[r]=0,答案就是-dis[l-1]
q.push(r);
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to, w = edge[i].w;
if (dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
if (!vis[v])vis[v] = 1, q.push(v);
}
}
}
}
int main()
{
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, u, v, w;
while (cin >> n)
{
memset(head, 0, sizeof(head));
num = 0;
l = INF, r = -INF;
for (int i = 1; i <= n; ++i)
{
cin >> u >> v >> w;
add(v, u - 1, -w);
l = min(l, u), r = max(r, v);
}
for (int i = l; i <= r; ++i)add(i, i - 1, 0), add(i - 1, i, 1);
spfa();
cout << -dis[l - 1] << endl;//为什么dis[l-1]不是dis[l],我们说过dis[l]包括l,因为我们求解结果完整形式是s[r]-s[l-1],如果是s[l]对于l这个点不能用
}
return 0;
}
最长路形式:左边>=右边
最长路在求最小值方面就很容易思考了,因为>=,所以每次更新左边的时候,左边都是“尽可能小”,所以从l遍历到r,s[r]-s[l-1]就是答案了,一般设s[l-1]=0
(两者代码就是小改一下的区别)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f; //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 5e4 + 10;
int head[N * 3], dis[N];
bool vis[N];
int num, l, r;
struct node
{
int next, to, w;
} edge[N * 3]; //最多建立N*3的边
void add(int u, int v, int w)
{
edge[++num].next = head[u];
edge[num].to = v;
edge[num].w = w;
head[u] = num;
}
void spfa()
{
queue<int>q;
memset(dis, -0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[l - 1] = 0; //从左边遍历到右边s[r]-s[l-1]就是答案了,一般设s[l-1]=0
q.push(l - 1);
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to, w = edge[i].w;
if (dis[v] < dis[u] + w)
{
dis[v] = dis[u] + w;
if (!vis[v])vis[v] = 1, q.push(v);
}
}
}
}
int main()
{
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, u, v, w;
while (cin >> n)
{
memset(head, 0, sizeof(head));
num = 0;
l = INF, r = -INF;
for (int i = 1; i <= n; ++i)
{
cin >> u >> v >> w;
add(u - 1, v, w);
l = min(l, u), r = max(r, v);
}
for (int i = l; i <= r; ++i)add(i, i - 1, -1), add(i - 1, i, 0);
spfa();
cout << dis[r] << endl;
}
return 0;
}
5,例题2:Cashier Employment
(虽然求最小值最长路比较简单,但是我最短路写多了,这里只写最短路,两者没什么区别的)
思路:
我们设s[i]表示1~i小时销售员总人数,a[i]表示要求的当天人数至少多少,b[i]表示从i时刻开始工作的员工人数:
容易得到的关系:
1,i>8:s[i]-s[i-8]>=a[i](不是s[i]-s[i-1],因为i-8及其前面的员工工作时间最多到i-1,所以s[i]-s[i-8]表示能够在i时间工作的人员总数。
2,i<=8:s[i]-s[i+16]+s[24]>=a[i](s[i+16]==s[i-8+24]),我们发现,跨天数,s[i]一定小于s[i+16]的,所以我们要补上一整天s[24]的值才成立不等式
3,0<=s[i]-s[i-1]<=b[i],因为第i天最多多来b[i]员工,所以不能比i-1天对于这个人数
1,我们注意到不等式混进脏东西了(s[24]),我们不知道他的值,因为s[24]就是我们要求的最小值呀!)。但是到这里暗示很明显了,二分s[24]最小值求解答案。
2,题目时间为0~23,我们改为1~24,0作为超级源点,因为差分图经常不连通,所以用他可以一次spfa跑所有点,令s[0]=0,那么每次二分都会限制s[24]-s[0]<=mid(答案)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f; //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 30;
int dis[N], head[N << 2], a[N], b[N], cnt[N];
bool vis[N];
int num;
struct node
{
int next, to, w;
} edge[N << 2];
void add(int u, int v, int w)
{
edge[++num].next = head[u];
edge[num].to = v;
edge[num].w = w;
head[u] = num;
}
void init()
{
memset(head, 0, sizeof(head));
memset(cnt, 0, sizeof(cnt));
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
num = 0;
}
//图不一定连通,所以建超级源点约束
bool spfa(int mid)
{
queue<int>q;
init();
add(0, 24, mid);//限制dis[24]不能大于mid
for (int i = 1; i <= 24; ++i)
{
add(i, i - 1, 0), add(i - 1, i, b[i]);
if (i > 8)add(i, i - 8, -a[i]);
else add(i, i + 16, mid - a[i]);
}
dis[0] = 0;
q.push(0);
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to, w = edge[i].w;
if (dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
if (!vis[v])
{
vis[v] = 1;
q.push(v);
cnt[v]++;
if (cnt[v] > 25)return 0;
}
}
}
}
if (mid == dis[24])//因为我们是要问这个mid可不可行,已经以mid==s[24]为前提建边,如果你最后跟我说s[24]他他他。。。变了,那肯定不可以呀
return 1;
else return 0;
}
int main()
{
int t, n, x;
cin >> t;
while (t--)
{
memset(b, 0, sizeof(b));
for (int i = 1; i <= 24; ++i)cin >> a[i];
cin >> n;//员工总数
for (int i = 1; i <= n; ++i)cin >> x, b[x + 1]++;
int l = 0, r = n;
int ans = INF;
while (l <= r)
{
int mid = (l + r) >> 1;
if (spfa(mid))
{
ans = mid, r = mid - 1;
}
else l = mid + 1;
}
if (ans < INF)cout << ans << endl;
else cout << "No Solution" << endl;
}
return 0;
}