对于差分约束理解
差分约束解决了一类不等式组的解的问题,如
(0)
x
1
−
x
0
≤
a
x_1 - x_0 \leq a \tag{0}
x1−x0≤a(0)
(1)
x
2
−
x
1
≤
b
x_2-x1\leq b \tag{1}
x2−x1≤b(1)
(2)
x
2
−
x
0
≤
c
x_2-x_0\leq c \tag{2}
x2−x0≤c(2)
然后我们需要求的是
x
2
−
x
0
x_2-x_0
x2−x0的最大值
M
a
x
Max
Max
经过观察我们可以发现将
(
0
)
+
(
1
)
(0)+(1)
(0)+(1)得到
(3)
x
2
−
x
0
≤
a
+
b
\color{red} x_2-x_0 \leq a+b \tag{3}
x2−x0≤a+b(3)
(2)
x
2
−
x
0
≤
c
x_2-x_0 \leq c \tag{2}
x2−x0≤c(2)
综上
M
a
x
=
m
i
n
{
a
+
b
,
c
}
Max = min\{ a+b,c\}
Max=min{a+b,c}
如果我们用几何的方式来表示这三个不等式的话我们会有新的发现
M
a
x
=
m
i
n
{
a
+
b
,
c
}
Max = min\{ a+b,c\}
Max=min{a+b,c}不就是从
0
0
0到
1
1
1的最短距离吗
综上我们可以得出差分约束的一种解法-利用最短路算法
差分约束系统与最短路算法
现在我们将目光放在如何构造差分约束系统,并使用最短路算法解决。
对于任意不等式组
x
1
−
x
0
≤
a
x
2
−
x
1
≤
b
x
2
−
x
0
≤
c
x_1 - x_0 \leq a \\ x_2-x1\leq b \\ x_2-x_0\leq c
x1−x0≤ax2−x1≤bx2−x0≤c
将其写成如下形式
x
i
−
x
j
≤
a
i
x_i-x_j \leq a_i
xi−xj≤ai
并对每一个不等式建立有向边
j
→
i
j\rightarrow i
j→i边权为
a
i
a_i
ai
求
x
u
−
x
v
x_u - x_v
xu−xv的最大值
=
>
=>
=>即求
v
→
u
v\rightarrow u
v→u的最短路径
最大值与最小值的转换
当我们需要求的是
x
u
−
x
v
x_u - x_v
xu−xv的最小值的时候该怎么办
x
u
−
x
v
≥
M
i
n
x_u-x_v \geq Min
xu−xv≥Min
需要将差分约束系统所有不等式组变为
x
j
−
x
i
≥
−
a
i
x_j-x_i \geq -a_i
xj−xi≥−ai
并对每一个不等式建立有向边
i
→
j
i\rightarrow j
i→j边权为
−
a
i
-a_i
−ai
求
x
u
−
x
v
x_u - x_v
xu−xv的最小值
=
>
=>
=>求
v
→
u
v\rightarrow u
v→u的最长路径
这种方法和上述最短路法是等价的
最短路算法SPFA
由于差分约束系统用存在负权边,传统的最短贪心策略Dijkstra算法并不能处理负权边。
再者由于负权边会出现负权环,SPFA算法可以对负权图进行判断。
SPFA算法的原理:利用队列不断从队列中选取出结点出来松弛,直到队列为空为止。
基本模板
bool SPFA(int from,int to) {
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
for(int i=0;i<maxn;i++) dist[i] = inf;
vis[from] = true;
dist[from] = 0;
queue<int> qu;
qu.push(from);
cnt[from]=1;
while(!qu.empty()) {
int u = qu.front();qu.pop();
vis[u] = false;
for(int i=head[u];~i;i=edge[i].next) {
int v = edge[i].v;
if(dist[v] > dist[u] + edge[i].cost) {
dist[v] = dist[u] + edge[i].cost;
if(!vis[v]) {
vis[v] = true;
qu.push(v);
if(++cnt[v]>n) return false;
}
}
}
}
return true;
}
v
i
s
[
]
vis[]
vis[]表示结点是否在队列中
d
i
s
t
[
]
dist[]
dist[]记录了源点到其他点的最短路径
c
n
t
[
]
cnt[]
cnt[]用于判断是否存在负权环,当一个结点进入队列超过
n
n
n次,即存在负权环
例题
例题1:ZOJ 2770
题意
给出
n
n
n个点表示
n
n
n个军营,
C
i
C_i
Ci表示第i个军营可容纳的士兵的最大值,接着给出
m
m
m条边
(
i
,
j
,
k
)
(i,j,k)
(i,j,k)表示从第
i
i
i到第
j
j
j个军营拥有的最少士兵数。求在满足以上条件下最少有多少士兵
题解
差分系统
设每个军营人数为
A
i
A_i
Ai,军营容量为
C
i
C_i
Ci,军营人数的前缀和为
S
n
S_n
Sn
-
A i ≤ C i Ai \leq Ci Ai≤Ci 写成前缀和的形式
S i − S i − 1 < = C i S_i - S_{i-1} <= C_i Si−Si−1<=Ci -
从第 i i i个军营到第 j j j个军营的人数和至少为 k k k
S j − S i − 1 ≥ k S_j - S_{i-1} \geq k Sj−Si−1≥k 转换为
S i − 1 − S j ≤ − k S_{i-1} - S_j \leq -k Si−1−Sj≤−k -
每个军营都必须有人 A i ≥ 0 A_i \geq 0 Ai≥0
S i − S i − 1 ≥ 0 S_i - S_{i-1} \geq 0 Si−Si−1≥0转换为
S i − 1 − S i ≤ 0 S_{i-1} - S_i \leq 0 Si−1−Si≤0
目标:求解 S n − S 0 S_n - S_0 Sn−S0的最小值,设最小值为 M M M
S n − S 0 ≥ M S_n - S_0 \geq M Sn−S0≥M
转换
S 0 − S n ≤ − M S_0 - S_n \leq -M S0−Sn≤−M于是我们只需要求解从点 n n n到点 0 0 0的最短路径 − M -M −M
去负号即为 M M M注:存在负环则为不可估计无解
综上得到的差分约束系统为
S
i
−
S
i
−
1
<
=
C
i
S
i
−
1
−
S
j
≤
−
k
S
i
−
1
−
S
i
≤
0
S_i - S_{i-1} <= C_i \\ S_{i-1} - S_j \leq -k \\ S_{i-1} - S_i \leq 0
Si−Si−1<=CiSi−1−Sj≤−kSi−1−Si≤0
T
a
r
g
e
t
:
S
0
−
S
n
≤
−
M
Target:S_0 - S_n \leq -M
Target:S0−Sn≤−M
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int maxn = 1e3+10;
const int maxm = 1e5+10;
struct Edge {
int v;
ll cost;
int next;
Edge(){}
Edge(int _v,ll _c,int _n):v(_v),cost(_c),next(_n){}
};
Edge edge[maxm];
int n,m;
int head[maxn],tot;
bool vis[maxn]; // 是否在队列
int cnt[maxn]; // 每个点的入队次数
ll dist[maxn]; // 最短路径
ll cot[maxn];
inline void addedge(int u,int v,ll val) {
edge[tot] = Edge(v,val,head[u]);
head[u] = tot++;
}
bool SPFA(int from,int to) {
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
for(int i=0;i<maxn;i++) dist[i] = inf;
vis[from] = true;
dist[from] = 0;
queue<int> qu;
qu.push(from);
cnt[from]=1;
while(!qu.empty()) {
int u = qu.front();qu.pop();
vis[u] = false;
for(int i=head[u];~i;i=edge[i].next) {
int v = edge[i].v;
if(dist[v] > dist[u] + edge[i].cost) {
dist[v] = dist[u] + edge[i].cost;
if(!vis[v]) {
vis[v] = true;
qu.push(v);
if(++cnt[v]>n) return false;
}
}
}
}
return true;
}
inline void init() {
memset(head,-1,sizeof(head));tot = 0;
memset(cot,0,sizeof(cot));
}
int main()
{
while(~scanf("%d%d",&n,&m)) {
init();
ll cost;
for(int i=0;i<n;i++) {
scanf("%lld",&cost);
cot[i+1] = cot[i] + cost;
addedge(i,i+1,cost);
addedge(i+1,i,0);
}
for(int i=0,x,y;i<m;i++) {
scanf("%d%d%lld",&x,&y,&cost);
addedge(y,x-1,-cost);
//addedge(x-1,y,cot[y]-cot[x-1]);
}
bool flag = SPFA(n,0);
if(flag) printf("%lld\n",-dist[0]);
else printf("Bad Estimations\n");
}
return 0;
}