差分约束系统与图论的联系
差分约束系统
1.何为差分约束系统?
如果一个系统由n个变量和m个约束条件组成,约束条件形如
x
i
x_i
xi -
x
j
x_j
xj <= k(i,j∈[1,n]),则成为差分约束系统。
例如:
x
1
x_1
x1-
x
3
x_3
x3 <= 5
x
1
x_1
x1-
x
2
x_2
x2 <= 2
x
2
x_2
x2-
x
3
x_3
x3 <= 1
2.差分约束系统求解
以上式差分约束系统为例,若想求
x
1
x_1
x1到
x
3
x_3
x3的最小值,我们可以由第一个约束条件看出
x
1
x_1
x1 -
x
3
x_3
x3的最大值是5,但通过把第二个与第三个不等式相加,我们可以得到:
x
1
x_1
x1 -
x
3
x_3
x3 <= 3,最后我们验证3才是最优解。
现在我们把约束条件
x
i
x_i
xi -
x
j
x_j
xj <= k移项,可得:
x
i
x_i
xi <=
x
j
x_j
xj + k,并设
x
1
x_1
x1 = 0再模拟上述过程,我们发现,当且仅当
x
i
x_i
xi <=
x
j
x_j
xj + k时,
x
i
x_i
xi的值被改变成了
x
j
x_j
xj + k,学习过最短路径的hxd相信已经发现了其中的奥秘。
在SPFA,dijkstra等最短路算法中,当
x
i
x_i
xi <=
x
j
x_j
xj + k时,我们进行两点之间的松弛操作,即更新源点到点
x
i
x_i
xi的最短路径为
x
j
x_j
xj + k。
3.差分约束系统与最短路
差分约束系统的解法用到了单源最短路径问题中的三角形不等式。对有向图中的一条边<u,v>来说,都有
d
i
s
[
v
]
≤
d
i
s
[
u
]
+
l
e
n
[
u
]
[
v
]
dis[v]≤dis[u]+len[u][v]
dis[v]≤dis[u]+len[u][v],其中
d
i
s
[
u
]
dis[u]
dis[u]表示源点到u结点的最短路径,v结点同理。
l
e
n
[
u
]
[
v
]
len[u][v]
len[u][v]表示,u与v的边的长度。
三角不等式是显然的,从源点到顶点v的最短路径长度小于等于从源点到顶点 u的最短路径长度加上边 <u,v> 的长度值。
4.与有向图结合的例子
将左侧的不等式转化为有向图可得右图。若求解
x
1
−
x
4
x_1 - x_4
x1−x4的最大值只需求解顶点
x
1
x_1
x1 到顶点
x
4
x_4
x4的最短路径即可。显然看出所求解为27,我们再将三个不等式相加,可以得到
x
1
−
x
4
<
=
27
x_1 - x_4 <= 27
x1−x4<=27,即验证了结论。
最短路算法SPFA
1.引入SPFA的意义
差分约束系统经常与最短路联系起来,与平时我们做的最短路不同的是。差分约束系统总是与带负权的最短路联系在一起,而dijkstra算法却没有办法解决带负权的最短路问题,这时SPFA就可以提供解决单源含负权最短路问题的思路,而且可以判断是否含有负环。
2.SPFA算法思想
SPFA与BFS的写法很类似,同样都需要一个队列。
①我们设数组d来表示最短路的值,
d
[
u
]
d[u]
d[u]表示源点到结点u的最短路径的长度。最开始,我们将源点s加入队列中,并设
d
[
s
]
=
0
d[s] = 0
d[s]=0,其他点设为INF。
②当队列不空时,我们进行循环,每次循环将队首元素取出,取出的结点设为u,并访问与u结点相连的所有结点,若存在边<u,v>且v结点不在队列中,我们就将v结点加入队列中,并对v结点做松弛操作。即如果
d
[
v
]
>
d
[
u
]
+
<
u
,
v
>
d[v] > d[u]+<u,v>
d[v]>d[u]+<u,v>,就令
d
[
v
]
=
d
[
u
]
+
<
u
,
v
>
d[v] = d[u] + <u,v>
d[v]=d[u]+<u,v>重复以上过程直至循环结束(即队列空)。
③SPFA算法结果:在循环过程中,如果一个结点进入队列的次数超过了结点个数N,则证明含有负环。若不存在负环,将
d
[
u
]
d[u]
d[u]输出就可以求得源点s到结点u的最短路的长度。
3.SPFA算法复杂度
SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,一般在1~2之间)。
4.例子
①我们设A为起点,初始化得
②与A相邻的结点有B、C,我们按算法的思想将dis与queue更新得:
③取出队首B,重复过程:
④一直循环,得如下分步:
应用
POJ3169.Layout 链接如下
快点我呀
思路:我们设d[i]为,第i头牛的位置,所以一定有
d
[
i
]
<
=
d
[
i
+
1
]
d[i]<=d[i+1]
d[i]<=d[i+1],对于没对关系好的牛,有
d
[
A
L
]
+
D
L
>
=
d
[
B
L
]
d[AL]+DL>=d[BL]
d[AL]+DL>=d[BL],对于关系不好的牛,有
d
[
A
D
]
+
D
D
<
=
d
[
B
D
]
d[AD]+DD<=d[BD]
d[AD]+DD<=d[BD],讲上面三个不等式处理成差分约束系统可以得到:
d
[
i
]
<
=
d
[
i
+
1
]
+
0
d[i] <= d[i+1]+0
d[i]<=d[i+1]+0
d
[
B
L
]
<
=
d
[
A
L
]
+
D
L
d[BL] <= d[AL] + DL
d[BL]<=d[AL]+DL
d
[
A
D
]
<
=
d
[
B
D
]
+
(
−
D
D
)
d[AD] <= d[BD] + (-DD)
d[AD]<=d[BD]+(−DD)
所以我们在顶点i+1向顶点i连接一条权值为0的边,在AL到BL连接一条权值为DL的边,在BD到AD连接一条权值为-DD的边,由于存在负边,所以使用SPFA算法。
最后,如果存在负圈,则输出-1,如果
d
[
N
]
=
I
N
F
d[N] = INF
d[N]=INF则1-N不可达,输出-2。
若可达且不存在负圈,则输出
d
[
N
]
d[N]
d[N]。
AC代码:
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#define INF 0x7fffffff
using namespace std;
const int maxn = 1e3+5;
const int maxm = 1e4+5;
struct edge{
int v,w;
edge(int v1,int w1):v(v1),w(w1){};
};
int d[maxn],cnt[maxn];bool vis[maxn];
int n,ml,md;
vector<edge> G[maxn];
queue<int> q;
void solve(){
fill(d+1,d+1+n,INF);
fill(vis+1,vis+1+n,false);
d[1] = 0;
q.push(1);
while(!q.empty()){
int u = q.front();q.pop();
vis[u] = false;
for(int i = 0;i < G[u].size();i++){
edge e = G[u][i];
int v = e.v;
int w = e.w;
if(d[v] > d[u] + w){
d[v] = d[u] + w;
if(!vis[v]){
if(++cnt[v] > n){
printf("-1");
return;
}
q.push(v);
vis[v] = true;
}
}
}
}
printf("%d",d[n] == INF ? -2 : d[n]);
}
int main(){
int u,v,w;
scanf("%d%d%d",&n,&ml,&md);
for(int i = 0;i < ml;i++){
scanf("%d%d%d",&u,&v,&w);
G[u].push_back(edge(v,w));
}
for(int i = 0;i < md;i++){
scanf("%d%d%d",&u,&v,&w);
G[v].push_back(edge(u,-w));
}
for(int i = 1;i < n;i++){
G[i+1].push_back(edge(i,0));
}
solve();
system("pause");
return 0;
}