BellmanFord单源最短路径路径算法差分约束系统ZOJ–2770

来源:互联网 发布:wps h5是什么软件 编辑:程序博客网 时间:2024/05/17 09:28

ZOJ-2770 Burn the Linked Camp题目的大意为:
陆逊侦查得知刘备将自己的军队分为N个营,从左到右编号为1,2,3…N。第i个营有最大士兵数Ci,通过一段时间的观察,陆逊可以估算至少有k个士兵住在第i到第j个营地,最后陆逊必须要估算刘备的军队至少有多少士兵,以便应对。

输入:
有多个测试案例,在每个测试案例的第一行,有两个整数N(0<n<=1000)和M(0 <= M = 10000)。第二行中,有n个整数C1…CN。然后M行,每行有三个整数i,j,k(0 < <= j <= N,0≤k<2 ^ 31),这意味着从i到j营士兵总数量至少是K.

输出:
对于每一个测试案例,在一行一个整数,输出:根据陆逊的观察估算刘备军队最少的士兵数。然而,陆逊估计在输入数据可能很不精确。如果他的估计是不真实的,输出“Bad Estimations”在单一的一行。

解题思路:
题目要求从观察结果中求出刘备军队里士兵的最小值。由于每个营地都有能容纳士兵的最大值,存在不等约束关系,此题可以考虑用差分约束系统解题。
差分约束系统:
如果一个系统由n个变量和m个约束条件组成,其中每个约束条件都形如:

Xj-Xi<=Bk   (i,j∈[1,n],k∈[1,m])

则称其为差分约束系统。

求解差分约束系统:
求解差分约束系统,可以转化成图论的单源最短路径问题,

Xj-Xi<=BkXj<=Xi+Bk

类似最短路径的三角不等式
distance[v]<= distance[u]+weight[u,v]
即distance[v] - distance[u]<=weight[u,v]
因此,以每个变量Xi为结点,对于约束条件
Xj-Xi<=Bk 即 Xj<=Xi+Bk
连接一条边(i,j),权值为Bk
再增加一个源点A,A与所有顶点相连,权值均为0,如果图中存在负环,则无解,否则,从源点到其它顶点的单源最短路径就是问题的一组可行解。

设有N个营地,编号为 i(i=1、2、3…N), K(i)代表第i(i=1、2、3…N)个营地的士兵数加上编号小于i的所有营地的士兵数。
则第j(j=1、2、3…N)个营地的士兵数为K(j)-K(j-1)
若M(j)表示营地j能容纳的最大士兵数,则有不等关系:

K(j)-K(j-1)<=M(j)

变换得

K(j)<=K(j-1)+M(j)

为了避免第一个营地的特殊情况,我们增加一个编号为0且士兵数为0的营地,即K(1)-K(0)=K(1).这个营地只是辅助,对结果不产生影响。
我们设每个编号i的营地为顶点i(i=0,1,2…N),我们添加一个源点,源点到所有其他顶点都有一条有向边,权值为0,不需要保存该点,只是辅助解题,保证图的连通性。则K(j)和K(j-1)分别为从源点到j,j-1两点的最短路径权值,若营地j的士兵数为M(j),则有K(j)<=K(j-1)+M(j),可以从j到j-1连接一条有向边,权值为M(j),问题转化为求从源点到顶点j的单源最短路径。
因为从源点到点j的最短路径必定满足K(j)<=K(j-1)+M(j)
同理,从营地i到j之间至少有X个士兵,则有不等关系:

K(j)-K(i-1)>=X

变换为

K(i-1)<=K(j)-X

于是,从顶点j到顶点i-1连一条有向边,权值为-X,求解源点到j点的最短路径时必定也满足该不等式K(i-1)<=K(j)-X

这里写图片描述

其中K(1),K(2)…K(N)为差分约束系统的一组解,当答案要求非负时,我们对每个解加上一个常数C便可。
最后源点到点N的最短路径便是问题的解,但实际上士兵数不能为负,因为求最少的士兵总数,我们可以加上一个最小的常数C,使得该组解非负。
当图中存在负权环时,问题无解。因为若存在负权环,不停地走这个环的话,最短路径趋向负无穷,显然不可能存在最短路径。
该题涉及负权边,用Bellman Ford算法求解最短路径较合适。

Bellman Ford算法思想是基于以下事实:

“两个点间如果存在最短路径,那么每个结点最多经过一次。”

也就是说,对n个结点的图,这条路不超过n-1条边。
如果一个结点经过了两个,说明我们走了一个圈:
如果该圈的权为正,显然不划算;
如果是负圈,不存在最短路径;
如果是零圈,去掉不影响最短路径。

路径边数上限为m的最短路径,可以通过由边数上限为m-1时的最短路径“外加一条边”来求,则最多只需要迭代n-1次就可以求出最短路径。

Bellman Ford算法伪代码如下:

//V(G)为图G的顶点集,E(G)为图G的边集for i=0 to |V(G)|    for 每条边(u,v)属于E(G)//松弛每条边    if(distance[v]>distance[u]+weight[u,v])      distance[v]=distance[u]+weight[u,v]    end forend forfor 每条边(u,v)属于E(G)//检查是否有负环    if(distance[v]>distance[u]+weight[u,v])      return falseend forreturn true

算法时间复杂度O(VE)。
该算法简单容易理解,缺点是时间复杂度较高,这里可以用队列进行优化,本文重点是最短路径算法如何应用在差分约束系统,便不再赘述。

Bellman Ford算法用到松弛技术:
维护一个源点到所有其它点的距离表distance[],松弛一条边(u,v)的过程包括测试我们是否可以通过结点u对目前到v结点的最短路径distance[v]进行修改,如果通过u到达v的路径更短,则更新distance[v]。这样便是保证每两个结点都有

distance[v]<= distance[u]+weight[u,v]

伪代码如下:

if(distance[v]> distance[u]+weight[u,v])//不符合就调整   distance[v] = distance[u]+weight[u,v]

代码:

#include<stdio.h>#include<vector>using namespace std;int *disList;//求最短路径时维护的距离表struct Edge{//边结构    int start;//起点    int end;//终点    int weight;//权值};vector<Edge> E;//存放边集的容器vector<Edge>::iterator it;//边的迭代器bool bellmanford(int N)//求每个顶点到源点的最短路径{    while(--N)//走N-1 条边    {        for (it = E.begin(); it != E.end();it++)//对每条边进行松弛        {            if (it->weight + disList[it->start]< disList[it->end])            {                disList[it->end] = it->weight + disList[it->start];            }        }    }    for (it = E.begin(); it != E.end(); it++)//若还能进行松弛,说明存在负环,无解    {        if (it->weight + disList[it->start]< disList[it->end])        {            return false;        }    }    return true;};void output(int N){    if (!bellmanford(N))printf("Bad Estimations\n");    else//根据实际情况,兵营士兵数不能为负,将所有解化为正数    {        int min = disList[0];        for (int i = 1; i <= N; i++)        {            if (min > disList[i])min = disList[i];        }        if (min < 0)        {            disList[N] = disList[N] - min;        }        printf("%d\n", -disList[N]);    }};int main(){    int N, M;    Edge e;    while (scanf("%d%d", &N,&M) ==2)    {        E.clear();//清空容器        disList = new int[N + 1];        for (int i = 0; i <= N;i++)//初始化距离表            disList[i]=0;        //数据输入        for (int i = 1; i <= N; i++)        {            scanf("%d", &e.weight);            e.start = i - 1;            e.end = i;            E.push_back(e);//营地i的最大士兵数为w,向矩阵图插入由i-1指向i权值为w的有向边        }        int a, b,c;        for (int i = 0; i < M; i++)        {            scanf("%d%d%d", &a,&b,&c);            e.start = b;            e.end = a - 1;            e.weight = -c;            E.push_back(e);//营地a与营地b之间至少士兵数为c,向矩阵图中插入由点b指向点a,权值为-c的有向边        }           output(N);//输出结果        delete[]disList;//释放内存    }    return 0;}

0 0