差分约束系统与图论的联系(最短路SPFA算法)

差分约束系统

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 x1x4的最大值只需求解顶点 x 1 x_1 x1 到顶点 x 4 x_4 x4的最短路径即可。显然看出所求解为27,我们再将三个不等式相加,可以得到 x 1 − x 4 < = 27 x_1 - x_4 <= 27 x1x4<=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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值