差分约束系统小结

一、什么是差分约束系统?#

1. 引例#

x1x02......(1)

x2x07......(2)

x3x08......(3)

x2x03......(4)

x3x22......(5)

给定n个变量和m个不等式,每个不等式形如xixj<=ak$(0 \le i, j < n, 0 \le k < m, a_k)x_{n-1} - x_0n = 4m = 5x_3-x_0$的最大值。

我们考虑暴力。

  1. (3)x3x08
  2. (2)+(5)x3x09
  3. (1)+(4)+(5)x3x07

所以有x3x0min{7,8,9}=7,即x3x0的最小值为7

当然,对于更大的数据,我们希望能用算法系统地处理,想一想,我们暴力计算的过程跟什么有点像?

看图,如果让我们求这张图的最短路,我们也许很快就能敲完一份完美的代码。

再仔细看看这张图,你会发现,这张图似乎与前面的不等式限制条件一一对应。

没错,我们需要用图论的知识把不等式关系转换成图中点与边之间的关系。

如若一个系统由n个变量和m个不等式组成,并且这m个不等式对应的系数矩阵中每一行有且仅有一个11,其它的都为0,这样的系统称为差分约束( difference constraints )系统。前面的不等式组同样可以表示成如图的系数矩阵。(摘自夜深人静写算法系列

[11001010100101100011](x0x1x2x3)(27832)

现在,我们思考如何转换。

二、代数与图形的关系及原理#

1.原理的感性理解#

对于一个单独的不等式xixjak来说,将这个等式移项,可得xixj+ak,这与单源最短路问题中的dis[v]dis[u]+w(u,v)很像啊。

也就是说,在求最短路时,如果出现dis[v]>dis[u]+w(u,v),我们就会更新dis[v],力求dis[v]dis[u]+w(u,v),这不与我们求解不等式的解如出同辙吗?

也就是说,形如xixjak的式子,我们连一条由ji,权值为ak的有向边,然后在图上跑一遍最短路,就能得到我们想要的答案。

2. 三角不等式#

我们来看一个简化的情况。

xBxAd1......(1)

xCxBd2......(2)

xCxAd3......(3)

我们同样想知道xCxA的最大值,(1)+(2),可得xCxAd1+d2,再有个(3),所以实质上我们在求min{(d1+d2),d3},把这个建个图你就会发现,min{(d1+d2),d3}正好对应了AC的最短路。

3. 解的有无#

因为我们已成功地将不等式关系转化成了图中的关系,所以当图异常结束的时候,就说明原不等式组无解。这里的异常结束包括最短路中的负权环,最长路中的正权环。

当然,若未更新我们想知道的点的值,就说明它未在差分约束系统里,所有取值都是可行的

只要最短(长)路算法正常结束并且所有变量都没有确定的值,那么该不等式必然有无限多组解,并且当前所求出的一组解必然是满足题目条件的边界解;一般情况下,我们会新建一个节点值为0,与其他点相连,有时题目会给出某些初始点的值。

4. 最大值与最小值的转化#

前面我们有意无意提到了最长路,也就是说,我把前面不等式反号,把变成,照样可以建图,然后跑最长路,思想与前面一样。

当然,也可以通过数学方法,两边乘个1,变成前面的最短路处理。

三、差分约束的应用&题目#

1. 线性约束:布局Layout#

大意:#

一维线上有n(2n1000)个点,有些点之间距离不能大于某个数,也有些点之间距离不能小于某个数,求第n个点到第1个点的距离最小是多少,若没有合法情况,输出1,若可以取无限大,输出2

分析:#

Pi表示i号位置到1的距离,那么我们可以知道,Pi单调不下降。

所以有第一类限制条件:P1P2...Pn1Pn

同时,它给出了MLPiPjDMDPiPjD,直接连边。这里的P1为0,求Pn即为最终答案。

有个坑点,首先应先判断整张图有没有环,即新建节点0连向所有节点,这样也不会对后面计算答案产生影响并且可以遍历完整张图。

然后再从1节点跑,若Pn未被更新,就说明最短路无限长,否则输出最短路的长度。

代码:#

Copy
#include <queue> #include <vector> #include <cstdio> #include <cstring> #include <algorithm> #define Re register using namespace std; const int MAX = 1e4 + 5; const int INF = 0x7f7f7f7f; const int RS = -2139062144; inline int read(){ int f = 1, x = 0; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9'); do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); return f * x; } struct Sakura { int to, nxt, w; }sak[MAX << 1]; int head[MAX], cnt; inline void add(int x, int y, int w) { ++cnt; sak[cnt].to = y, sak[cnt].w = w, sak[cnt].nxt = head[x], head[x] = cnt; } int n, ml, md; struct SPFA { int dis[MAX], cnt[MAX], vis[MAX]; inline bool Run(int st) { for (int i = 0;i <= n; ++i) dis[i] = 1e9, cnt[i] = 0, vis[i] = 0; queue <int> Q; dis[st] = 0; vis[st] = 1; Q.push(st); while (!Q.empty()) { int u = Q.front(); Q.pop(); vis[u] = 0; ++cnt[u]; if (cnt[u] >= n) { puts("-1"); exit(0); } for (int i = head[u];i;i = sak[i].nxt) { int v = sak[i].to, w = sak[i].w; if (dis[v] > dis[u] + w) { dis[v] = dis[u] + w; if (!vis[v]) { Q.push(v); vis[v] = 1; } } } } return 1; } }Spfa; int main(){ n = read(), ml = read(), md = read(); for (int i = 1;i <= ml; ++i) { int a = read(), b = read(), d = read(); add(a, b, d); } for (int i = 1;i <= md; ++i) { int a = read(), b = read(), d = read(); add(b, a, -d); } for (int i = 2;i <= n; ++i) { add(i, i - 1, 0); } for (int i = 1;i <= n; ++i) { add(0, i, 0); } Spfa.Run(0); Spfa.Run(1); if (Spfa.dis[n] == 1e9) { printf("-2"); return 0; } else printf("%d", Spfa.dis[n]); return 0; }

2. 区间约束:Intervals #

大意:#

给定n个区间约束条件,要求[ai,bi]中至少有ci个数,求构造出的总个数最少。

分析:#

区间上,设mj表示区间为[j,j]上的个数为多少,即有:0mj1,且j=aibimjci

考虑前缀和Sj,所以就有:0SjSj11SbiSai1ci

然后就可以做了。

Copy
#include <queue> #include <vector> #include <cstdio> #include <cstring> #include <algorithm> #define Re register using namespace std; const int MAX = 200000 + 5; const int INF = 0x7f7f7f7f; inline int read(){ int f = 1, x = 0; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9'); do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); return f * x; } struct Sakura { int to, nxt, w; }sak[MAX << 1]; int head[MAX], cnt; inline void add(int x, int y, int w) { ++cnt; sak[cnt].to = y, sak[cnt].w = w, sak[cnt].nxt = head[x], head[x] = cnt; } int t, n, maxx, minn = INF; struct SPFA { int dis[MAX]; int vis[MAX]; queue <int> Q; inline void Run(int st) { for (int i = 0;i <= maxx; ++i) dis[i] = -INF; memset(vis, 0, sizeof vis); dis[st] = 0; vis[st] = 1; Q.push(st); while (!Q.empty()) { int u = Q.front(); Q.pop(); vis[u] = 0; for (int i = head[u];i;i = sak[i].nxt) { int v = sak[i].to, w = sak[i].w; if (dis[v] < dis[u] + w) { dis[v] = dis[u] + w; if (!vis[v]) { Q.push(v); vis[v] = 1; } } } } } }Spfa; int main(){ t = read(); while (t --) { n = read(); memset(head, 0, sizeof head); cnt = 0; for (int i = 1;i <= n; ++i) { int a = read() + 1, b = read() + 1, w = read(); minn = min(minn, a - 1); maxx = max(maxx, b); add(a - 1, b, w); } for (int i = 1;i <= maxx; ++i) add(i - 1, i, 0), add(i, i - 1, -1); Spfa.Run(0); printf("%d\n", Spfa.dis[maxx]); if (t != 0) puts(""); } return 0; }

3. 未知约束:出纳员问题#

大意:略#

分析:#

限制条件有点多,慢慢来整理一下。

numi表示在i时刻最多能招到多少人,Ri表示在i时刻至少需要多少人。

首先,设ai表示在i时刻雇佣的人数,则有0ainumi

其次,有ai7+ai6+...+aiRi(7i23),还有a0+a1+...+ai+ai+17+...+a23Ri(0i<7)

Siai的前缀和,所以就有:

  1. 0SiSi1num[i](0i23)
  2. SiSi8Ri(8i23)
  3. SiSi+16RiS23(0i<8)

前两个好办,关键是第三个。

看到我把S23移到了右边,也许你会猜到什么了吧?

没错,枚举S23,从0n枚举,看是否出现可行解,当然记得要确保图中的节点23的值就是你枚举的值,我们可以新建一个值为0的节点h,就有S23Sh=你枚举的值,把它拆成两个不等式,搞定。

然后,思考,当招i个人能满足要求时,i+1个人也能满足要求,直接二分求解。

Copy
#include <queue> #include <vector> #include <cstdio> #include <cstring> #include <algorithm> #define Re register using namespace std; const int MAX = 200000 + 5; const int INF = 0x7f7f7f7f; inline int read(){ int f = 1, x = 0; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9'); do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); return f * x; } struct Sakura { int to, nxt, w; }sak[MAX << 1]; int head[MAX], cnt; inline void add(int x, int y, int w) { ++cnt; sak[cnt].to = y, sak[cnt].w = w, sak[cnt].nxt = head[x], head[x] = cnt; } int T, R[MAX], num[MAX], n; struct SPFA { int dis[MAX], cnt[MAX], vis[MAX]; inline bool Run(int st) { memset(dis, 128, sizeof dis); memset(cnt, 0, sizeof cnt); memset(vis, 0, sizeof vis); queue <int> Q; dis[st] = 0; vis[st] = 1; Q.push(st); while (!Q.empty()) { int u = Q.front(); Q.pop(); vis[u] = 0; for (int i = head[u];i;i = sak[i].nxt) { int v = sak[i].to, w = sak[i].w; if (dis[v] < dis[u] + w) { dis[v] = dis[u] + w; if (!vis[v]) { Q.push(v); vis[v] = 1; if (++cnt[v] > n) return 0; } } } } return 1; } }Spfa; int main(){ T = read(); while (T --) { bool can = 0; for (int i = 1;i <= 24; ++i) { R[i] = read(); if (R[i]) can = 1; } for (int i = 0;i <= 24; ++i) num[i] = 0; n = read(); for (int i = 1;i <= n; ++i) { int x = read() + 1; num[x] ++; } if (!can) { printf("0\n"); continue; } bool flag = 0; int l = 0, r = n; while (l < r) { int mid = (l + r) >> 1; memset(head, 0, sizeof head); cnt = 0; for (int i = 9;i <= 24; ++i) add(i - 8, i, R[i]); for (int i = 1;i <= 8; ++i) add(i + 16, i, R[i] - mid); for (int i = 1;i <= 24; ++i) add(i, i - 1, -num[i]), add(i - 1, i, 0); add(0, 24, mid); add(24, 0, -mid); if (Spfa.Run(0)) r = mid, flag = 1; else l = mid + 1; } if (flag) printf("%d\n", r); else { memset(head, 0, sizeof head); cnt = 0; for (int i = 9;i <= 24; ++i) add(i - 8, i, R[i]); for (int i = 1;i <= 8; ++i) add(i + 16, i, R[i] - r); for (int i = 1;i <= 24; ++i) add(i, i - 1, -num[i]), add(i - 1, i, 0); add(0, 24, r); add(24, 0, -r); if (Spfa.Run(0)) printf("%d\n", r); else printf("No Solution\n"); } } return 0; }

4. 合理转化:糖果 #

大意:#

略略略~

分析:#

把前面弄懂就会做了。

代码:#

Copy
#include<cstdio> #include<cstdlib> #include<cstring> #define ll long long #define Re register #define Min(a, b) ((a) < (b) ? (a) : (b)) const int MAX = 500000 + 5; inline int read(){ int f = 1, x = 0;char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0'||ch>'9'); do {x = x*10+ch-'0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); return f*x; } struct sakura { int to, nxt, w; }sak[MAX]; int head[MAX], cnt; inline void add(int x, int y, int w) { ++cnt; sak[cnt].to = y, sak[cnt].nxt = head[x], sak[cnt].w = w, head[x] = cnt; } ll ans; int n, k, q[MAX], h, t = 1, dis[MAX], vis[MAX], cnts[MAX]; inline bool SPFA(int st) { q[1] = st; dis[st] = 0; vis[st] = 1; while (h < t) { int u = q[++h]; cnts[u]++; if (cnts[u] > n - 1) return 0; vis[u] = 0; for (int i = head[u];i;i = sak[i].nxt) { int v = sak[i].to, w = sak[i].w; if (dis[v] < dis[u] + w) { dis[v] = dis[u] + w; if (!vis[v]) q[++t] = v, vis[v] = 1; } } } return 1; } int main(){ n = read(), k = read(); for (int i = 1;i <= k; ++i) { int x = read(), a = read(), b = read(); switch(x) { case 1:{ add(a, b, 0); add(b, a, 0); break; } case 2:{ if (a == b) { puts("-1"); return 0; } add(a, b, 1); break; } case 3:{ add(b, a, 0); break; } case 4:{ if (a == b) { puts("-1"); return 0; } add(b, a, 1); break; } case 5:{ add(a, b, 0); break; } } } for (int i = n;i >= 1; --i) { add(n + 1, i, 1); } if (SPFA(n + 1)) { for (int i = 1;i <= n; ++i) { ans += dis[i]; } printf("%lld", ans); } else printf("-1"); return 0; }
posted @   SilentEAG  阅读(132)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
CONTENTS